Compare commits

..

70 Commits

Author SHA1 Message Date
75780252eb Add documentation on translation directives 2025-11-28 16:54:18 +01:00
45d7d1614a Add documentation on use of translation provider Loco 2025-11-28 12:41:59 +01:00
009846853b Merge config into single file for translations 2025-11-28 12:41:46 +01:00
7f71fae295 Correct translation in ThirdpartyBundle 2025-11-28 10:23:37 +01:00
cf0cdfb3a0 Set variable in .env.local file 2025-11-28 09:58:11 +01:00
0380bc59de Complete translations ChillActivityBundle 2025-11-28 09:57:51 +01:00
1cc59e33cc Complete translations ChillCalendarBundle 2025-11-28 09:56:14 +01:00
30e87c3369 Complete translations ChillBudgetBundle 2025-11-28 09:51:50 +01:00
eec65a7943 Complete translations ChillAsideActivityBundle 2025-11-28 09:49:01 +01:00
a56ef214a3 Complete translations ChillReportBundle 2025-11-28 09:47:18 +01:00
d328b8bcaf Add translations ChillWopiBundle 2025-11-28 09:43:33 +01:00
2e3ef233e5 Add translations ChillThirdpartyBundle 2025-11-28 09:42:30 +01:00
7a95c444dd Add translations ChillTaskBundle 2025-11-28 09:37:06 +01:00
6e2215964d Complete translations ChillPersonBundle 2025-11-28 09:23:08 +01:00
e63fe793f8 Add dutch translations in ChillPersonBundle 2025-11-26 15:07:21 +01:00
0e1f9dcb93 Complete translations ChillMainBundle 2025-11-26 14:45:57 +01:00
e30847dfcd Install loco into project + config todo 2025-11-26 12:47:18 +01:00
65025dc7da ChillMainBundle translations 2025-11-26 11:37:16 +01:00
e3d80ac468 ChillJobBundle translations 2025-11-26 09:54:35 +01:00
55195c7bde ChillEventBundle translations 2025-11-26 09:50:54 +01:00
eb88063cee ChillDocStoreBundle translations 2025-11-26 09:40:09 +01:00
3eea8abbeb ChillBudgetBundle translations 2025-11-26 06:28:23 +01:00
5335b62679 ChillDocGeneratorBundle translations 2025-11-26 06:26:27 +01:00
31fb428703 ChillCalendarBundle translations 2025-11-26 06:26:11 +01:00
604b2361d8 ChillCustomFieldsBundle translations 2025-11-26 06:25:49 +01:00
93e76952dd ChillActivityBundle translations 2025-11-26 06:25:25 +01:00
0d32810d0d Change position and color of confirm parcours button 2025-11-24 15:13:16 +01:00
b221ad1621 Merge branch '466-set-main-user-activity' into 'master'
Associate activity's creator as a participant by default, and retro-actively append the creator to each activity

Closes #466

See merge request Chill-Projet/chill-bundles!924
2025-11-24 09:23:12 +00:00
a96e9d5377 Associate activity's creator as a participant by default, and retro-actively append the creator to each activity 2025-11-24 09:23:12 +00:00
54b73128c3 Merge branch '470-alphabetical-order-admin' into 'master'
Alphabetically order userJobs and mainLocations within user creation form

Closes #470

See merge request Chill-Projet/chill-bundles!926
2025-11-24 09:18:03 +00:00
5c0cb01fdc Alphabetically order userJobs and mainLocations within user creation form 2025-11-24 09:18:03 +00:00
26d9b55c6d Update to v4.8.1 2025-11-20 16:19:52 +01:00
add9249502 Merge branch '471-fix-inactive-user-group-api' into 'master'
Hide inactive user groups in API responses

Closes #471

See merge request Chill-Projet/chill-bundles!927
2025-11-19 15:19:25 +00:00
380d48c43a Hide inactive user groups in API responses 2025-11-19 15:19:25 +00:00
c7d7c3ac6f Add missing 'id' paramater in path 2025-11-19 13:48:35 +01:00
7eb895c0e1 Insert name of file as the document title when uploading 2025-11-19 13:33:51 +01:00
e1b91ebbfd Release v4.8.0 2025-11-17 15:11:14 +01:00
2139b53fb0 Merge branch '449-scope-picker-form-label' into 'master'
Remove the label if there is only one scope and no scope picking field is displayed.

Closes #449

See merge request Chill-Projet/chill-bundles!911
2025-11-17 10:48:16 +00:00
a43181d60d Remove the label if there is only one scope and no scope picking field is displayed. 2025-11-17 10:48:15 +00:00
04bc1c5de8 Merge branch '463-update-calendar-with-accepted-invites' into 'master'
Update calendar with accepted invites

Closes #463

See merge request Chill-Projet/chill-bundles!921
2025-11-14 14:22:59 +00:00
0a07d68b6d Merge branch '461-calendar-items-clickable' into 'master'
Resolve "Rendre le rendez-vous clicable dans la page "mes rendez-vous""

Closes #461

See merge request Chill-Projet/chill-bundles!919
2025-11-14 14:08:04 +00:00
fccd29e3c7 Resolve "Rendre le rendez-vous clicable dans la page "mes rendez-vous"" 2025-11-14 14:08:03 +00:00
274ee94196 Merge branch '420-localisation-variable' into 'master'
Ajouter une variable de localisation aux utilisateurs

Closes #420

See merge request Chill-Projet/chill-bundles!904
2025-11-14 13:52:33 +00:00
799d04142e Ajouter une variable de localisation aux utilisateurs 2025-11-14 13:52:33 +00:00
dfe8d8b0bf Merge branch 'accessibility/improve-login-page' into 'master'
Improve accessibility on the login page

See merge request Chill-Projet/chill-bundles!922
2025-11-14 10:16:08 +00:00
82f347b93a Improve accessibility on the login page 2025-11-14 10:16:08 +00:00
635efd6f1d Update calendar with accepted invites 2025-11-12 17:01:09 +01:00
869880d8f3 Revert "Display calendar items linked to person within search results"
This reverts commit f7ea7e4dbf.
2025-11-12 13:08:54 +01:00
f7ea7e4dbf Display calendar items linked to person within search results 2025-11-12 13:00:52 +01:00
0a58e05230 Update chill bundles to v4.7.0 2025-11-10 16:47:38 +01:00
68c83223dd Merge branch '455-results-objectives-display-order' into 'master'
Resolve "Action d'accompagnement - afficher les objectifs avant les résultats"

Closes #455

See merge request Chill-Projet/chill-bundles!913
2025-11-07 16:23:53 +00:00
c28bd22560 Resolve "Action d'accompagnement - afficher les objectifs avant les résultats" 2025-11-07 16:23:52 +00:00
a5ef2475fb Merge branch 'text-wrapping-badges' into 'master'
Wrap text when it is too long within badges

See merge request Chill-Projet/chill-bundles!918
2025-11-07 14:48:48 +00:00
86dd9bfb80 Wrap text when it is too long within badges 2025-11-07 15:18:02 +01:00
c28670f0fd Merge branch '457-merge-thirdparty-bug' into 'master'
Fix the fusion of thirdparty properties that are located in another schema...

Closes #457

See merge request Chill-Projet/chill-bundles!916
2025-11-07 10:50:03 +00:00
9e2c030224 Fix the fusion of thirdparty properties that are located in another schema... 2025-11-07 10:50:03 +00:00
a706c6f337 fix: set back to true suggestion of referrer when creating notification for
accompanyingPeriodWorkDocument
2025-11-06 16:18:33 +01:00
bc63b489ee Merge branch '285-cancel-calendar' into 'master'
Permettre d'annuler un rendez-vous

Closes #285

See merge request Chill-Projet/chill-bundles!775
2025-11-06 15:07:11 +00:00
a4cfc6a178 Permettre d'annuler un rendez-vous 2025-11-06 15:07:11 +00:00
f75d1da3b1 Merge branch '385-invitation-list' into 'master'
Add user invitation list page

Closes #385

See merge request Chill-Projet/chill-bundles!866
2025-11-06 12:06:15 +00:00
b8b68e5e5a Rename page title key for invitations list to align with translation standards
- Replaced hardcoded title 'My invitations list' with 'invite.list.title' translation key.
2025-11-06 13:00:38 +01:00
ae5ba67064 Update UserMenuBuilder to adjust menu labels and sort order
- Renamed 'My invitations list' to 'invite.list.title'.
- Updated the sort order for 'My calendar' from 9 to 8, to place "invitation list" just after the calendar list
2025-11-06 13:00:28 +01:00
bfe4dd3aec Merge branch 'master' into 385-invitation-list 2025-11-06 12:14:21 +01:00
74c9eb5585 Rector corrections 2025-09-30 16:23:27 +02:00
f93c7e014f Add test for MyInvitationsController.php 2025-09-30 15:45:26 +02:00
e6a799abc4 Add translation for invitation list page title 2025-09-30 15:30:38 +02:00
68a0ef7115 Reorganize templates to allow re-use of _list.html.twig within listByUser.html.twig template 2025-09-30 15:30:20 +02:00
1675c56f3d Fix order of paginator parameters passed to findBy method 2025-09-30 15:29:41 +02:00
675e8450fc WIP: switch from ACLAware to normal repository usage 2025-09-30 14:34:47 +02:00
4ffd7034d0 feat: add invitation list
- Introduced `MyInvitationsController` for managing user invitations
- Added `InviteACLAwareRepository` and its interface for handling invite data operations
- Created views for listing and displaying user-specific invitations
- Updated user menu to include "My invitations list" option
2025-09-30 14:34:47 +02:00
564 changed files with 25861 additions and 41451 deletions

View File

@@ -1,7 +0,0 @@
kind: DX
body: |
Send notifications log to dedicated channel, if it exists
time: 2025-10-27T15:00:53.309372316+01:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: |
Upgrade import of address list to the last version of compiled addresses of belgian-best-address
time: 2024-05-30T16:00:03.440767606+02:00
custom:
Issue: ""

View File

@@ -1,6 +0,0 @@
kind: Feature
body: |
Upgrade CKEditor and refactor configuration with use of typescript
time: 2024-05-31T19:02:42.776662753+02:00
custom:
Issue: ""

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Create invitation list in user menu
time: 2025-08-08T12:08:02.446361367+02:00
custom:
Issue: "385"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Admin interface for Motive entity
time: 2025-10-07T15:59:45.597029709+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Add an admin interface for Motive entity
time: 2025-10-22T11:15:52.13937955+02:00
custom:
Issue: ""
SchemaChange: Add columns or tables

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Add columns for comments linked to an activity in the activity list export
time: 2025-10-29T15:25:10.493968528+01:00
custom:
Issue: "404"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: 'Fix: display also social actions linked to parents of the selected social issue'
time: 2025-10-29T12:43:55.008647232+01:00
custom:
Issue: "451"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: 'Fix: export actions and their results in csv even when action does not have any goals attached to it.'
time: 2025-10-29T14:38:36.195220844+01:00
custom:
Issue: "453"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Fix the possibility to delete a workflow
time: 2025-11-04T13:51:08.113234488+01:00
custom:
Issue: ""
SchemaChange: Drop or rename table or columns, or enforce new constraint that must be manually fixed

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Fix suggestion of referrer when creating notification for accompanyingPeriodWorkDocument
time: 2025-11-06T16:16:05.861813041+01:00
custom:
Issue: "428"
SchemaChange: No schema change

View 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

View File

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

View File

@@ -1,6 +0,0 @@
kind: UX
body: Improve the ux for selecting whether user wants to be notified of the final step of a workflow or all steps
time: 2025-10-29T11:08:04.077020411+01:00
custom:
Issue: "542"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: UX
body: Expand timeSpent choices for evaluation document and translate them to user locale or fallback 'fr'
time: 2025-10-30T18:09:19.373907522+01:00
custom:
Issue: ""
SchemaChange: No schema change

View 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

View 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

21
.changes/v4.7.0.md Normal file
View 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
View 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
View 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

View File

@@ -19,11 +19,11 @@ max_line_length = 80
[COMMIT_EDITMSG]
max_line_length = 0
[*.{js,vue,ts}]
[*.{js, vue, ts}]
indent_size = 2
indent_style = space
[*.rst]
indent_size = 3
indent_style = space
[.rst]
ident_size = 3
ident_style = space

4
.env
View File

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

View File

@@ -234,9 +234,11 @@ This must be a decision made by a human, not by an AI. Every AI task must abort
#### Running Tests
The tests are run from the project's root (not from the bundle's root: so, do not change the directory to any bundle directory before running tests).
The tests are run from the project's root (not from the bundle's root).
```bash
# Run all tests
vendor/bin/phpunit
# Run a specific test file
vendor/bin/phpunit path/to/TestFile.php

View File

@@ -1,4 +0,0 @@
{
"tabWidth": 2,
"useTabs": false
}

30
.vscode/launch.json vendored
View File

@@ -1,30 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Chill Debug",
"type": "php",
"request": "launch",
"port": 9000,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
},
"preLaunchTask": "symfony"
},
{
"name": "Yarn Encore Dev (Watch)",
"type": "node-terminal",
"request": "launch",
"command": "yarn encore dev --watch",
"cwd": "${workspaceFolder}"
}
],
"compounds": [
{
"name": "Chill Debug + Yarn Encore Dev (Watch)",
"configurations": ["Chill Debug", "Yarn Encore Dev (Watch)"]
}
]
}

23
.vscode/tasks.json vendored
View File

@@ -1,23 +0,0 @@
{
"tasks": [
{
"type": "shell",
"command": "symfony",
"args": [
"server:start",
"--allow-http",
"--no-tls",
"--port=8000",
"--allow-all-ip",
"-d"
],
"label": "symfony"
},
{
"type": "shell",
"command": "yarn",
"args": ["encore", "dev", "--watch"],
"label": "webpack"
}
]
}

View File

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

View File

@@ -54,7 +54,7 @@ Arborescence:
- person
- personvendee
- household_edit_metadata
- index.ts
- index.js
```
## Organisation des feuilles de styles

View File

@@ -1,12 +1,7 @@
import {
trans,
setLocale,
getLocale,
setLocaleFallbacks,
} from "./ux-translator";
import { trans, setLocale, setLocaleFallbacks } from "./ux-translator";
setLocaleFallbacks({ en: "fr", nl: "fr", fr: "en" });
setLocale("fr");
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
setLocale('fr');
export { trans, getLocale };
export * from "../var/translations";
export { trans };
export * from '../var/translations';

View File

@@ -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",
@@ -133,7 +134,6 @@
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
"Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
"Chill\\Utils\\Rector\\": "utils/rector/src"
}
},

View File

@@ -34,7 +34,6 @@ return [
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
Chill\TicketBundle\ChillTicketBundle::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],

View File

@@ -1,5 +1,5 @@
chill_main:
available_languages: [ '%env(resolve:LOCALE)%', 'en' ]
available_languages: [ '%env(resolve:LOCALE)%', 'en', 'nl' ]
available_countries: ['BE', 'FR']
top_banner:
visible: false

View File

@@ -1,5 +1,5 @@
chill_doc_store:
use_driver: local_storage
use_driver: openstack
local_storage:
storage_path: '%kernel.project_dir%/var/storage'
openstack:

View File

@@ -1,5 +0,0 @@
chill_ticket:
ticket:
person_per_ticket: one # One of "one"; "many"
response_time_exceeded_delay: PT12H

View File

@@ -14,7 +14,6 @@ doctrine_migrations:
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
'Chill\Migrations\Report': '@ChillReportBundle/migrations'
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
all_or_nothing:
true

View File

@@ -66,7 +66,6 @@ framework:
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async
'Chill\TicketBundle\Messenger\PostTicketUpdateMessage': async
# end of routes added by chill-bundles recipes
# Route your messages to the transports
# 'App\Message\YourMessage': async

View File

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

View File

@@ -1,4 +0,0 @@
framework:
default_locale: '%env(resolve:LOCALE)%'
translator:
fallbacks: [ '%env(resolve:LOCALE)%' ]

View File

@@ -1,2 +0,0 @@
chill_ticket_bundle:
resource: '@ChillTicketBundle/config/routes.yaml'

View File

@@ -11,94 +11,24 @@
Create a new bundle
*******************
Create your own bundle is not a trivial task.
The easiest way to achieve this is seems to be :
1. Prepare a fresh installation of the chill project, in a new directory
2. Create a new bundle in this project, in the src directory
3. Initialize a git repository **at the root bundle**, and create your initial commit.
4. Register the bundle with composer/packagist. If you do not plan to distribute your bundle with packagist, you may use a custom repository for achieve this [#f1]_
5. Move to a development installation, made as described in the :ref:`installation-for-development` section, and add your new repository to the composer.json file
6. Work as :ref:`usual <editing-code-and-commiting>`
.. warning::
This part of the doc is not yet tested
Create a new directory with Bundle class
----------------------------------------
.. code-block:: bash
mkdir -p src/Bundle/ChillSomeBundle/src/config
mkdir -p src/Bundle/ChillSomeBundle/src/Controller
Add a bundle file
.. code-block:: php
<?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\SomeBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillSomeBundle extends Bundle {}
And a route file:
.. code-block:: yaml
chill_ticket_controller:
resource: '@ChillTicketBundle/Controller/'
type: annotation
Register the new psr-4 namespace
--------------------------------
In composer.json, add the new psr4 namespace
.. code-block:: diff
{
"autoload": {
"psr-4": {
+ "Chill\\SomeBundle\\": "src/Bundle/ChillSomeBundle/src",
}
}
}
TODO
Register the bundle
-------------------
Register in the file :code:`config/bundles.php`:
.. code-block:: php
Vendor\Bundle\YourBundle\YourBundle::class => ['all' => true],
And import routes in :code:`config/routes/chill_some_bundle.yaml`:
.. code-block:: yaml
chill_ticket_bundle:
resource: '@ChillSomeBundle/config/routes.yaml'
Add the doctrine_migration namespace
------------------------------------
Add the namespace to :code:`config/packages/doctrine_migrations_chill.yaml`
.. code-block:: diff
doctrine_migrations:
migrations_paths:
+ 'Chill\Some\Ticket': '@ChillSomeBundle/migrations'
Dump autoloading
----------------
.. code-block:: bash
symfony composer dump-autoload
.. rubric:: Footnotes
.. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software.

View 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.

View 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 Locos 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

View File

@@ -41,7 +41,6 @@
"typescript": "^5.6.3",
"typescript-eslint": "^8.13.0",
"vue-loader": "^17.0.0",
"vue-tsc": "^3.1.3",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
@@ -81,12 +80,12 @@
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress",
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml src/Bundle/ChillTicketBundle/chill.api.specs.yaml> templates/api/specs.yaml",
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
"specs-create-dir": "mkdir -p templates/api",
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
"version": "node --version",
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
},
"private": true
}

View File

@@ -58,10 +58,6 @@
<!-- temporarily removed, the time to find a fix -->
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
</testsuite>
<testsuite name="TicketBundle">
<directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
</testsuite>
<!--
<testsuite name="ReportBundle">
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>

View File

@@ -1,8 +0,0 @@
In this directory, you find an example of file for the command `chill:main:ticket_motives_import`.
This file contains a list of ticket motives to import into the system. Each entry is a dictionary with two keys: `code` and `label`. The `code` key contains the unique code for the ticket motive, and the `label` key contains the human-readable label for the ticket motive.
The `stored_objects` key contains the documents that will be associated with the tickets. They must be found in the same directory.
The command `chill:main:ticket_motives_import` uses this file to import the specified ticket motives into the system.

View File

@@ -1,136 +0,0 @@
- label:
fr: Appel famille pour annonce de décès
urgent: false
supplementary_informations:
- label:
fr: Date du décès
- label:
fr: lieu du décès (domicile ou hôpital)
- label:
fr: nom de lhôpital
- label:
fr: service concerné
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 2_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 3_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 4_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 'Appel famille pour annonce absence : hospitalisation ou consultation'
urgent: false
supplementary_informations:
- label:
fr: Quel hôpital
- label:
fr: quel service
- label:
fr: pour quelles raisons
- label:
fr: 'consultation : date et heure'
- label:
fr: hospitalisation complète ou HDJ
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 5_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 6_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 7_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 'Appel famille pour annonce absence : interruption de prise en charge'
urgent: false
supplementary_informations:
- label:
fr: Pour quelles raisons ? Date
- label:
fr: durée
- label:
fr: accord médical ?
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 8_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 9_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 10_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 'Appel famille pour annonce absence : changement dadresse'
urgent: false
supplementary_informations:
- label:
fr:
- label:
fr: Pourquoi ? Pour combien de temps ? Besoin dun relais des soins ? Nouvelle adresse ?
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 11_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 12_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 13_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: Appel famille pour altération de létat général du patient
urgent: true
supplementary_informations:
- label:
fr: Recherche des symptômes
- label:
fr: Attentes par rapport à la demande
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 14_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 15_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 16_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: Appel famille pour prise en charge de la douleur
urgent: true
supplementary_informations:
- label:
fr: Localisation douleur
- label:
fr: Horaire dernier passage
- label:
fr: Traitements en cours
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 17_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 18_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 19_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: Appel famille pour information sur la date de prise en charge
urgent: false
supplementary_informations: []
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 20_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 21_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 22_doc_20250402_Pelotons flux externes consolidés.pdf

View File

@@ -1,6 +0,0 @@
In this directory, you find an example of file for the command `chill:main:override_translation`.
This file contains a list of translations to override in the translation catalogue. Each entry is a dictionary with two keys: `from` and `to`. The `from` key contains the original translation string, and the `to` key contains the replacement string.
The command `chill:main:override_translation` uses this file to generate a new translation catalogue with the specified overrides applied.

View File

@@ -1,8 +0,0 @@
- {from: "de l'usager", to: "du patient"}
- {from: "l'usager", to: "le patient"}
- {from: "L'usager", to: "Le patient"}
- {from: "d'usagers", to: "de patients"}
- {from: "usagers", to: "patients"}
- {from: "Usagers", to: "Patients"}
- {from: "usager", to: "patient"}
- {from: "Usager", to: "Patient"}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<template>
<concerned-groups v-if="hasPerson" />
<social-issues-acc v-if="hasSocialIssues" />
<location v-if="hasLocation" />
<concerned-groups v-if="hasPerson" />
<social-issues-acc v-if="hasSocialIssues" />
<location v-if="hasLocation" />
</template>
<script>
@@ -10,12 +10,12 @@ import SocialIssuesAcc from "./components/SocialIssuesAcc.vue";
import Location from "./components/Location.vue";
export default {
name: "App",
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
components: {
ConcernedGroups,
SocialIssuesAcc,
Location,
},
name: "App",
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
components: {
ConcernedGroups,
SocialIssuesAcc,
Location,
},
};
</script>

View File

@@ -1,43 +1,46 @@
<template>
<teleport to="#add-persons" v-if="isComponentVisible">
<div class="flex-bloc concerned-groups" :class="getContext">
<persons-bloc
v-for="bloc in contextPersonsBlocs"
:key="bloc.key"
:bloc="bloc"
:bloc-width="getBlocWidth"
:set-persons-in-bloc="setPersonsInBloc"
/>
</div>
<div
v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"
>
<ul class="list-suggest add-items inline">
<li
v-for="(p, i) in suggestedEntities"
@click="addSuggestedEntity(p)"
:key="`suggestedEntities-${i}`"
<teleport to="#add-persons" v-if="isComponentVisible">
<div class="flex-bloc concerned-groups" :class="getContext">
<persons-bloc
v-for="bloc in contextPersonsBlocs"
:key="bloc.key"
:bloc="bloc"
:bloc-width="getBlocWidth"
:set-persons-in-bloc="setPersonsInBloc"
/>
</div>
<div
v-if="
getContext === 'accompanyingCourse' &&
suggestedEntities.length > 0
"
>
<person-text v-if="p.type === 'person'" :person="p" />
<span v-else>{{ p.text }}</span>
</li>
</ul>
</div>
<ul class="list-suggest add-items inline">
<li
v-for="(p, i) in suggestedEntities"
@click="addSuggestedEntity(p)"
:key="`suggestedEntities-${i}`"
>
<person-text v-if="p.type === 'person'" :person="p" />
<span v-else>{{ p.text }}</span>
</li>
</ul>
</div>
<ul class="record_actions">
<li class="add-persons">
<add-persons
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
v-bind:key="addPersons.key"
v-bind:options="addPersonsOptions"
@addNewPersons="addNewPersons"
ref="addPersons"
>
</add-persons>
</li>
</ul>
</teleport>
<ul class="record_actions">
<li class="add-persons">
<add-persons
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
v-bind:key="addPersons.key"
v-bind:options="addPersonsOptions"
@addNewPersons="addNewPersons"
ref="addPersons"
>
</add-persons>
</li>
</ul>
</teleport>
</template>
<script>
@@ -46,208 +49,208 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import {
ACTIVITY_BLOC_PERSONS,
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
ACTIVITY_BLOC_THIRDPARTY,
ACTIVITY_BLOC_USERS,
ACTIVITY_ADD_PERSONS,
trans,
ACTIVITY_BLOC_PERSONS,
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
ACTIVITY_BLOC_THIRDPARTY,
ACTIVITY_BLOC_USERS,
ACTIVITY_ADD_PERSONS,
trans,
} from "translator";
export default {
name: "ConcernedGroups",
components: {
AddPersons,
PersonsBloc,
PersonText,
},
setup() {
return {
trans,
ACTIVITY_ADD_PERSONS,
};
},
data() {
return {
personsBlocs: [
{
key: "persons",
title: trans(ACTIVITY_BLOC_PERSONS),
persons: [],
included: false,
name: "ConcernedGroups",
components: {
AddPersons,
PersonsBloc,
PersonText,
},
setup() {
return {
trans,
ACTIVITY_ADD_PERSONS,
};
},
data() {
return {
personsBlocs: [
{
key: "persons",
title: trans(ACTIVITY_BLOC_PERSONS),
persons: [],
included: false,
},
{
key: "personsAssociated",
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
persons: [],
included: window.activity
? window.activity.activityType.personsVisible !== 0
: true,
},
{
key: "personsNotAssociated",
title: "activity.bloc_persons_not_associated",
persons: [],
included: window.activity
? window.activity.activityType.personsVisible !== 0
: true,
},
{
key: "thirdparty",
title: trans(ACTIVITY_BLOC_THIRDPARTY),
persons: [],
included: window.activity
? window.activity.activityType.thirdPartiesVisible !== 0
: true,
},
{
key: "users",
title: trans(ACTIVITY_BLOC_USERS),
persons: [],
included: window.activity
? window.activity.activityType.usersVisible !== 0
: true,
},
],
addPersons: {
key: "activity",
},
};
},
computed: {
isComponentVisible() {
return window.activity
? window.activity.activityType.personsVisible !== 0 ||
window.activity.activityType.thirdPartiesVisible !== 0 ||
window.activity.activityType.usersVisible !== 0
: true;
},
{
key: "personsAssociated",
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
persons: [],
included: window.activity
? window.activity.activityType.personsVisible !== 0
: true,
...mapState({
persons: (state) => state.activity.persons,
thirdParties: (state) => state.activity.thirdParties,
users: (state) => state.activity.users,
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
}),
...mapGetters(["suggestedEntities"]),
getContext() {
return this.accompanyingCourse ? "accompanyingCourse" : "person";
},
{
key: "personsNotAssociated",
title: "activity.bloc_persons_not_associated",
persons: [],
included: window.activity
? window.activity.activityType.personsVisible !== 0
: true,
contextPersonsBlocs() {
return this.personsBlocs.filter((bloc) => bloc.included !== false);
},
{
key: "thirdparty",
title: trans(ACTIVITY_BLOC_THIRDPARTY),
persons: [],
included: window.activity
? window.activity.activityType.thirdPartiesVisible !== 0
: true,
addPersonsOptions() {
let optionsType = [];
if (window.activity) {
if (window.activity.activityType.personsVisible !== 0) {
optionsType.push("person");
}
if (window.activity.activityType.thirdPartiesVisible !== 0) {
optionsType.push("thirdparty");
}
if (window.activity.activityType.usersVisible !== 0) {
optionsType.push("user");
}
} else {
optionsType = ["person", "thirdparty", "user"];
}
return {
type: optionsType,
priority: null,
uniq: false,
button: {
size: "btn-sm",
},
};
},
{
key: "users",
title: trans(ACTIVITY_BLOC_USERS),
persons: [],
included: window.activity
? window.activity.activityType.usersVisible !== 0
: true,
getBlocWidth() {
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
},
],
addPersons: {
key: "activity",
},
};
},
computed: {
isComponentVisible() {
return window.activity
? window.activity.activityType.personsVisible !== 0 ||
window.activity.activityType.thirdPartiesVisible !== 0 ||
window.activity.activityType.usersVisible !== 0
: true;
},
...mapState({
persons: (state) => state.activity.persons,
thirdParties: (state) => state.activity.thirdParties,
users: (state) => state.activity.users,
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
}),
...mapGetters(["suggestedEntities"]),
getContext() {
return this.accompanyingCourse ? "accompanyingCourse" : "person";
mounted() {
this.setPersonsInBloc();
},
contextPersonsBlocs() {
return this.personsBlocs.filter((bloc) => bloc.included !== false);
},
addPersonsOptions() {
let optionsType = [];
if (window.activity) {
if (window.activity.activityType.personsVisible !== 0) {
optionsType.push("person");
}
if (window.activity.activityType.thirdPartiesVisible !== 0) {
optionsType.push("thirdparty");
}
if (window.activity.activityType.usersVisible !== 0) {
optionsType.push("user");
}
} else {
optionsType = ["person", "thirdparty", "user"];
}
return {
type: optionsType,
priority: null,
uniq: false,
button: {
size: "btn-sm",
methods: {
setPersonsInBloc() {
let groups;
if (this.accompanyingCourse) {
groups = this.splitPersonsInGroups();
}
this.personsBlocs.forEach((bloc) => {
if (this.accompanyingCourse) {
switch (bloc.key) {
case "personsAssociated":
bloc.persons = groups.personsAssociated;
bloc.included = true;
break;
case "personsNotAssociated":
bloc.persons = groups.personsNotAssociated;
bloc.included = true;
break;
}
} else {
switch (bloc.key) {
case "persons":
bloc.persons = this.persons;
bloc.included = true;
break;
}
}
switch (bloc.key) {
case "thirdparty":
bloc.persons = this.thirdParties;
break;
case "users":
bloc.persons = this.users;
break;
}
}, groups);
},
splitPersonsInGroups() {
let personsAssociated = [];
let personsNotAssociated = this.persons;
let participations = this.getCourseParticipations();
this.persons.forEach((person) => {
participations.forEach((participation) => {
if (person.id === participation.id) {
//console.log(person.id);
personsAssociated.push(person);
personsNotAssociated = personsNotAssociated.filter(
(p) => p !== person,
);
}
});
});
return {
personsAssociated: personsAssociated,
personsNotAssociated: personsNotAssociated,
};
},
getCourseParticipations() {
let participations = [];
this.accompanyingCourse.participations.forEach((participation) => {
if (!participation.endDate) {
participations.push(participation.person);
}
});
return participations;
},
addNewPersons({ selected, modal }) {
console.log("@@@ CLICK button addNewPersons", selected);
selected.forEach((item) => {
this.$store.dispatch("addPersonsInvolved", item);
}, this);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
this.setPersonsInBloc();
},
addSuggestedEntity(person) {
this.$store.dispatch("addPersonsInvolved", {
result: person,
type: "person",
});
this.setPersonsInBloc();
},
};
},
getBlocWidth() {
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
},
},
mounted() {
this.setPersonsInBloc();
},
methods: {
setPersonsInBloc() {
let groups;
if (this.accompanyingCourse) {
groups = this.splitPersonsInGroups();
}
this.personsBlocs.forEach((bloc) => {
if (this.accompanyingCourse) {
switch (bloc.key) {
case "personsAssociated":
bloc.persons = groups.personsAssociated;
bloc.included = true;
break;
case "personsNotAssociated":
bloc.persons = groups.personsNotAssociated;
bloc.included = true;
break;
}
} else {
switch (bloc.key) {
case "persons":
bloc.persons = this.persons;
bloc.included = true;
break;
}
}
switch (bloc.key) {
case "thirdparty":
bloc.persons = this.thirdParties;
break;
case "users":
bloc.persons = this.users;
break;
}
}, groups);
},
splitPersonsInGroups() {
let personsAssociated = [];
let personsNotAssociated = this.persons;
let participations = this.getCourseParticipations();
this.persons.forEach((person) => {
participations.forEach((participation) => {
if (person.id === participation.id) {
//console.log(person.id);
personsAssociated.push(person);
personsNotAssociated = personsNotAssociated.filter(
(p) => p !== person,
);
}
});
});
return {
personsAssociated: personsAssociated,
personsNotAssociated: personsNotAssociated,
};
},
getCourseParticipations() {
let participations = [];
this.accompanyingCourse.participations.forEach((participation) => {
if (!participation.endDate) {
participations.push(participation.person);
}
});
return participations;
},
addNewPersons({ selected, modal }) {
console.log("@@@ CLICK button addNewPersons", selected);
selected.forEach((item) => {
this.$store.dispatch("addPersonsInvolved", item);
}, this);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
this.setPersonsInBloc();
},
addSuggestedEntity(person) {
this.$store.dispatch("addPersonsInvolved", {
result: person,
type: "person",
});
this.setPersonsInBloc();
},
},
};
</script>

View File

@@ -1,29 +1,29 @@
<template>
<li>
<span :title="person.text" @click.prevent="$emit('remove', person)">
<span class="chill_denomination">
<person-text :person="person" :is-cut="true" />
</span>
</span>
</li>
<li>
<span :title="person.text" @click.prevent="$emit('remove', person)">
<span class="chill_denomination">
<person-text :person="person" :is-cut="true" />
</span>
</span>
</li>
</template>
<script>
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
export default {
name: "PersonBadge",
props: ["person"],
components: {
PersonText,
},
// computed: {
// textCutted() {
// let more = (this.person.text.length > 15) ?'…' : '';
// return this.person.text.slice(0,15) + more;
// }
// },
emits: ["remove"],
name: "PersonBadge",
props: ["person"],
components: {
PersonText,
},
// computed: {
// textCutted() {
// let more = (this.person.text.length > 15) ?'…' : '';
// return this.person.text.slice(0,15) + more;
// }
// },
emits: ["remove"],
};
</script>

View File

@@ -1,38 +1,38 @@
<template>
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
<div class="item-row">
<div class="item-col">
<h4>{{ $t(bloc.title) }}</h4>
</div>
<div class="item-col">
<ul class="list-suggest remove-items">
<person-badge
v-for="person in bloc.persons"
:key="person.id"
:person="person"
@remove="removePerson"
/>
</ul>
</div>
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
<div class="item-row">
<div class="item-col">
<h4>{{ $t(bloc.title) }}</h4>
</div>
<div class="item-col">
<ul class="list-suggest remove-items">
<person-badge
v-for="person in bloc.persons"
:key="person.id"
:person="person"
@remove="removePerson"
/>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import PersonBadge from "./PersonBadge.vue";
export default {
name: "PersonsBloc",
components: {
PersonBadge,
},
props: ["bloc", "setPersonsInBloc", "blocWidth"],
methods: {
removePerson(item) {
console.log("@@ CLICK remove person: item", item);
this.$store.dispatch("removePersonInvolved", item);
this.setPersonsInBloc();
name: "PersonsBloc",
components: {
PersonBadge,
},
props: ["bloc", "setPersonsInBloc", "blocWidth"],
methods: {
removePerson(item) {
console.log("@@ CLICK remove person: item", item);
this.$store.dispatch("removePersonInvolved", item);
this.setPersonsInBloc();
},
},
},
};
</script>

View File

@@ -1,32 +1,32 @@
<template>
<teleport to="#location">
<div class="mb-3 row">
<label :class="locationClassList">
{{ trans(ACTIVITY_LOCATION) }}
</label>
<div class="col-sm-8">
<VueMultiselect
name="selectLocation"
id="selectLocation"
label="name"
track-by="id"
open-direction="top"
:multiple="false"
:searchable="true"
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
:custom-label="customLabel"
:select-label="trans(MULTISELECT_SELECT_LABEL)"
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
:options="availableLocations"
group-values="locations"
group-label="locationGroup"
v-model="location"
/>
<new-location v-bind:available-locations="availableLocations" />
</div>
</div>
</teleport>
<teleport to="#location">
<div class="mb-3 row">
<label :class="locationClassList">
{{ trans(ACTIVITY_LOCATION) }}
</label>
<div class="col-sm-8">
<VueMultiselect
name="selectLocation"
id="selectLocation"
label="name"
track-by="id"
open-direction="top"
:multiple="false"
:searchable="true"
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
:custom-label="customLabel"
:select-label="trans(MULTISELECT_SELECT_LABEL)"
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
:options="availableLocations"
group-values="locations"
group-label="locationGroup"
v-model="location"
/>
<new-location v-bind:available-locations="availableLocations" />
</div>
</div>
</teleport>
</template>
<script>
@@ -35,60 +35,60 @@ import VueMultiselect from "vue-multiselect";
import NewLocation from "./Location/NewLocation.vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import {
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
} from "translator";
export default {
name: "Location",
components: {
NewLocation,
VueMultiselect,
},
setup() {
return {
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
};
},
data() {
return {
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
};
},
computed: {
...mapState(["activity", "availableLocations"]),
...mapGetters(["suggestedEntities"]),
location: {
get() {
return this.activity.location;
},
set(value) {
this.$store.dispatch("updateLocation", value);
},
name: "Location",
components: {
NewLocation,
VueMultiselect,
},
},
methods: {
labelAccompanyingCourseLocation(value) {
return `${value.address.text} (${localizeString(value.locationType.title)})`;
setup() {
return {
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
};
},
customLabel(value) {
return value.locationType
? value.name
? value.name === "__AccompanyingCourseLocation__"
? this.labelAccompanyingCourseLocation(value)
: `${value.name} (${localizeString(value.locationType.title)})`
: localizeString(value.locationType.title)
: "";
data() {
return {
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
};
},
computed: {
...mapState(["activity", "availableLocations"]),
...mapGetters(["suggestedEntities"]),
location: {
get() {
return this.activity.location;
},
set(value) {
this.$store.dispatch("updateLocation", value);
},
},
},
methods: {
labelAccompanyingCourseLocation(value) {
return `${value.address.text} (${localizeString(value.locationType.title)})`;
},
customLabel(value) {
return value.locationType
? value.name
? value.name === "__AccompanyingCourseLocation__"
? this.labelAccompanyingCourseLocation(value)
: `${value.name} (${localizeString(value.locationType.title)})`
: localizeString(value.locationType.title)
: "";
},
},
},
};
</script>

View File

@@ -1,114 +1,123 @@
<template>
<div>
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-create" @click="openModal">
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</a>
</li>
</ul>
<div>
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-create" @click="openModal">
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</a>
</li>
</ul>
<teleport to="body">
<modal
v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false"
>
<template #header>
<h3 class="modal-title">
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</h3>
</template>
<template #body>
<form>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">
{{ e }}
</li>
</ul>
</div>
<teleport to="body">
<modal
v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false"
>
<template #header>
<h3 class="modal-title">
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</h3>
</template>
<template #body>
<form>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">
{{ e }}
</li>
</ul>
</div>
<div class="form-floating mb-3">
<select
class="form-select form-select-lg"
id="type"
required
v-model="selectType"
>
<option selected disabled value="">
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
</option>
<option v-for="t in locationTypes" :value="t" :key="t.id">
{{ localizeString(t.title) }}
</option>
</select>
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label>
</div>
<div class="form-floating mb-3">
<select
class="form-select form-select-lg"
id="type"
required
v-model="selectType"
>
<option selected disabled value="">
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
</option>
<option
v-for="t in locationTypes"
:value="t"
:key="t.id"
>
{{ localizeString(t.title) }}
</option>
</select>
<label>{{
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
}}</label>
</div>
<div class="form-floating mb-3">
<input
class="form-control form-control-lg"
id="name"
v-model="inputName"
placeholder
/>
<label for="name">{{
trans(ACTIVITY_LOCATION_FIELDS_NAME)
}}</label>
</div>
<div class="form-floating mb-3">
<input
class="form-control form-control-lg"
id="name"
v-model="inputName"
placeholder
/>
<label for="name">{{
trans(ACTIVITY_LOCATION_FIELDS_NAME)
}}</label>
</div>
<add-address
:context="addAddress.context"
:options="addAddress.options"
:addressChangedCallback="submitNewAddress"
v-if="showAddAddress"
ref="addAddress"
/>
<add-address
:context="addAddress.context"
:options="addAddress.options"
:addressChangedCallback="submitNewAddress"
v-if="showAddAddress"
ref="addAddress"
/>
<div class="form-floating mb-3" v-if="showContactData">
<input
class="form-control form-control-lg"
id="phonenumber1"
v-model="inputPhonenumber1"
placeholder
/>
<label for="phonenumber1">{{
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
}}</label>
</div>
<div class="form-floating mb-3" v-if="hasPhonenumber1">
<input
class="form-control form-control-lg"
id="phonenumber2"
v-model="inputPhonenumber2"
placeholder
/>
<label for="phonenumber2">{{
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
}}</label>
</div>
<div class="form-floating mb-3" v-if="showContactData">
<input
class="form-control form-control-lg"
id="email"
v-model="inputEmail"
placeholder
/>
<label for="email">{{
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
}}</label>
</div>
</form>
</template>
<template #footer>
<button class="btn btn-save" @click.prevent="saveNewLocation">
{{ trans(SAVE) }}
</button>
</template>
</modal>
</teleport>
</div>
<div class="form-floating mb-3" v-if="showContactData">
<input
class="form-control form-control-lg"
id="phonenumber1"
v-model="inputPhonenumber1"
placeholder
/>
<label for="phonenumber1">{{
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
}}</label>
</div>
<div class="form-floating mb-3" v-if="hasPhonenumber1">
<input
class="form-control form-control-lg"
id="phonenumber2"
v-model="inputPhonenumber2"
placeholder
/>
<label for="phonenumber2">{{
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
}}</label>
</div>
<div class="form-floating mb-3" v-if="showContactData">
<input
class="form-control form-control-lg"
id="email"
v-model="inputEmail"
placeholder
/>
<label for="email">{{
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
}}</label>
</div>
</form>
</template>
<template #footer>
<button
class="btn btn-save"
@click.prevent="saveNewLocation"
>
{{ trans(SAVE) }}
</button>
</template>
</modal>
</teleport>
</div>
</template>
<script>
@@ -119,236 +128,237 @@ import { getLocationTypes } from "../../api";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import {
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
trans,
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
trans,
} from "translator";
export default {
name: "NewLocation",
components: {
Modal,
AddAddress,
},
setup() {
return {
trans,
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
};
},
props: ["availableLocations"],
data() {
return {
errors: [],
selected: {
type: null,
name: null,
addressId: null,
phonenumber1: null,
phonenumber2: null,
email: null,
},
locationTypes: [],
modal: {
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
},
addAddress: {
options: {
button: {
text: {
create: "activity.create_address",
edit: "activity.edit_address",
},
size: "btn-sm",
},
title: {
create: "activity.create_address",
edit: "activity.edit_address",
},
},
context: {
target: {
//name, id
},
edit: false,
addressId: null,
defaults: window.addaddress,
},
},
};
},
computed: {
...mapState(["activity"]),
selectType: {
get() {
return this.selected.type;
},
set(value) {
this.selected.type = value;
},
name: "NewLocation",
components: {
Modal,
AddAddress,
},
inputName: {
get() {
return this.selected.name;
},
set(value) {
this.selected.name = value;
},
},
inputEmail: {
get() {
return this.selected.email;
},
set(value) {
this.selected.email = value;
},
},
inputPhonenumber1: {
get() {
return this.selected.phonenumber1;
},
set(value) {
this.selected.phonenumber1 = value;
},
},
inputPhonenumber2: {
get() {
return this.selected.phonenumber2;
},
set(value) {
this.selected.phonenumber2 = value;
},
},
hasPhonenumber1() {
return (
this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""
);
},
showAddAddress() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.addressRequired !== "never") {
cond = true;
}
}
return cond;
},
showContactData() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.contactData !== "never") {
cond = true;
}
}
return cond;
},
},
mounted() {
this.getLocationTypesList();
},
methods: {
localizeString,
checkForm() {
let cond = true;
this.errors = [];
if (!this.selected.type) {
this.errors.push("Type de localisation requis");
cond = false;
} else {
if (
this.selected.type.addressRequired === "required" &&
!this.selected.addressId
) {
this.errors.push("Adresse requise");
cond = false;
}
if (
this.selected.type.contactData === "required" &&
!this.selected.phonenumber1
) {
this.errors.push("Numéro de téléphone requis");
cond = false;
}
if (
this.selected.type.contactData === "required" &&
!this.selected.email
) {
this.errors.push("Adresse email requise");
cond = false;
}
}
return cond;
},
getLocationTypesList() {
getLocationTypes().then((results) => {
this.locationTypes = results.filter(
(t) => t.availableForUsers === true,
);
});
},
openModal() {
this.modal.showModal = true;
},
saveNewLocation() {
if (this.checkForm()) {
let body = {
type: "location",
name: this.selected.name,
locationType: {
id: this.selected.type.id,
type: "location-type",
},
phonenumber1: this.selected.phonenumber1,
phonenumber2: this.selected.phonenumber2,
email: this.selected.email,
setup() {
return {
trans,
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
};
if (this.selected.addressId) {
body = Object.assign(body, {
address: {
id: this.selected.addressId,
},
props: ["availableLocations"],
data() {
return {
errors: [],
selected: {
type: null,
name: null,
addressId: null,
phonenumber1: null,
phonenumber2: null,
email: null,
},
});
}
makeFetch("POST", "/api/1.0/main/location.json", body)
.then((response) => {
this.$store.dispatch("addAvailableLocationGroup", {
locationGroup: "Localisations nouvellement créées",
locations: [response],
});
this.$store.dispatch("updateLocation", response);
this.modal.showModal = false;
})
.catch((error) => {
if (error.name === "ValidationException") {
for (let v of error.violations) {
this.errors.push(v);
}
} else {
this.errors.push("An error occurred");
locationTypes: [],
modal: {
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
},
addAddress: {
options: {
button: {
text: {
create: "activity.create_address",
edit: "activity.edit_address",
},
size: "btn-sm",
},
title: {
create: "activity.create_address",
edit: "activity.edit_address",
},
},
context: {
target: {
//name, id
},
edit: false,
addressId: null,
defaults: window.addaddress,
},
},
};
},
computed: {
...mapState(["activity"]),
selectType: {
get() {
return this.selected.type;
},
set(value) {
this.selected.type = value;
},
},
inputName: {
get() {
return this.selected.name;
},
set(value) {
this.selected.name = value;
},
},
inputEmail: {
get() {
return this.selected.email;
},
set(value) {
this.selected.email = value;
},
},
inputPhonenumber1: {
get() {
return this.selected.phonenumber1;
},
set(value) {
this.selected.phonenumber1 = value;
},
},
inputPhonenumber2: {
get() {
return this.selected.phonenumber2;
},
set(value) {
this.selected.phonenumber2 = value;
},
},
hasPhonenumber1() {
return (
this.selected.phonenumber1 !== null &&
this.selected.phonenumber1 !== ""
);
},
showAddAddress() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.addressRequired !== "never") {
cond = true;
}
}
});
}
return cond;
},
showContactData() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.contactData !== "never") {
cond = true;
}
}
return cond;
},
},
submitNewAddress(payload) {
this.selected.addressId = payload.addressId;
this.addAddress.context.addressId = payload.addressId;
this.addAddress.context.edit = true;
mounted() {
this.getLocationTypesList();
},
methods: {
localizeString,
checkForm() {
let cond = true;
this.errors = [];
if (!this.selected.type) {
this.errors.push("Type de localisation requis");
cond = false;
} else {
if (
this.selected.type.addressRequired === "required" &&
!this.selected.addressId
) {
this.errors.push("Adresse requise");
cond = false;
}
if (
this.selected.type.contactData === "required" &&
!this.selected.phonenumber1
) {
this.errors.push("Numéro de téléphone requis");
cond = false;
}
if (
this.selected.type.contactData === "required" &&
!this.selected.email
) {
this.errors.push("Adresse email requise");
cond = false;
}
}
return cond;
},
getLocationTypesList() {
getLocationTypes().then((results) => {
this.locationTypes = results.filter(
(t) => t.availableForUsers === true,
);
});
},
openModal() {
this.modal.showModal = true;
},
saveNewLocation() {
if (this.checkForm()) {
let body = {
type: "location",
name: this.selected.name,
locationType: {
id: this.selected.type.id,
type: "location-type",
},
phonenumber1: this.selected.phonenumber1,
phonenumber2: this.selected.phonenumber2,
email: this.selected.email,
};
if (this.selected.addressId) {
body = Object.assign(body, {
address: {
id: this.selected.addressId,
},
});
}
makeFetch("POST", "/api/1.0/main/location.json", body)
.then((response) => {
this.$store.dispatch("addAvailableLocationGroup", {
locationGroup: "Localisations nouvellement créées",
locations: [response],
});
this.$store.dispatch("updateLocation", response);
this.modal.showModal = false;
})
.catch((error) => {
if (error.name === "ValidationException") {
for (let v of error.violations) {
this.errors.push(v);
}
} else {
this.errors.push("An error occurred");
}
});
}
},
submitNewAddress(payload) {
this.selected.addressId = payload.addressId;
this.addAddress.context.addressId = payload.addressId;
this.addAddress.context.edit = true;
},
},
},
};
</script>

View File

@@ -1,38 +1,38 @@
<template>
<span class="inline-choice">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
v-model="selected"
name="action"
:id="action.id"
:value="action"
/>
<label class="form-check-label" :for="action.id">
<span class="badge bg-light text-dark" :title="action.text">{{
action.text
}}</span>
</label>
</div>
</span>
<span class="inline-choice">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
v-model="selected"
name="action"
:id="action.id"
:value="action"
/>
<label class="form-check-label" :for="action.id">
<span class="badge bg-light text-dark" :title="action.text">{{
action.text
}}</span>
</label>
</div>
</span>
</template>
<script>
export default {
name: "CheckSocialAction",
props: ["action", "selection"],
emits: ["updateSelected"],
computed: {
selected: {
set(value) {
this.$emit("updateSelected", value);
},
get() {
return this.selection;
},
name: "CheckSocialAction",
props: ["action", "selection"],
emits: ["updateSelected"],
computed: {
selected: {
set(value) {
this.$emit("updateSelected", value);
},
get() {
return this.selection;
},
},
},
},
};
</script>
@@ -41,13 +41,25 @@ export default {
@import "ChillPersonAssets/chill/scss/mixins";
@import "ChillMainAssets/chill/scss/chill_variables";
span.badge {
@include badge_social($social-action-color);
font-size: 95%;
margin-bottom: 5px;
margin-right: 1em;
max-width: 100%; /* Adjust as needed */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@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;
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>

View File

@@ -1,36 +1,38 @@
<template>
<span class="inline-choice">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
v-model="selected"
name="issue"
:id="issue.id"
:value="issue"
/>
<label class="form-check-label" :for="issue.id">
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
</label>
</div>
</span>
<span class="inline-choice">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
v-model="selected"
name="issue"
:id="issue.id"
:value="issue"
/>
<label class="form-check-label" :for="issue.id">
<span class="badge bg-chill-l-gray text-dark">{{
issue.text
}}</span>
</label>
</div>
</span>
</template>
<script>
export default {
name: "CheckSocialIssue",
props: ["issue", "selection"],
emits: ["updateSelected"],
computed: {
selected: {
set(value) {
this.$emit("updateSelected", value);
},
get() {
return this.selection;
},
name: "CheckSocialIssue",
props: ["issue", "selection"],
emits: ["updateSelected"],
computed: {
selected: {
set(value) {
this.$emit("updateSelected", value);
},
get() {
return this.selection;
},
},
},
},
};
</script>
@@ -39,9 +41,24 @@ export default {
@import "ChillPersonAssets/chill/scss/mixins";
@import "ChillMainAssets/chill/scss/chill_variables";
span.badge {
@include badge_social($social-issue-color);
font-size: 95%;
margin-bottom: 5px;
margin-right: 1em;
@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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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']]);

View File

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

View File

@@ -1,74 +1,76 @@
import { EventInput } from "@fullcalendar/core";
import {
DateTime,
Location,
User,
UserAssociatedInterface,
DateTime,
Location,
User,
UserAssociatedInterface,
} from "../../../ChillMainBundle/Resources/public/types";
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
export interface CalendarRange {
id: number;
endDate: DateTime;
startDate: DateTime;
user: User;
location: Location;
createdAt: DateTime;
createdBy: User;
updatedAt: DateTime;
updatedBy: User;
id: number;
endDate: DateTime;
startDate: DateTime;
user: User;
location: Location;
createdAt: DateTime;
createdBy: User;
updatedAt: DateTime;
updatedBy: User;
}
export interface CalendarRangeCreate {
user: UserAssociatedInterface;
startDate: DateTime;
endDate: DateTime;
location: Location;
user: UserAssociatedInterface;
startDate: DateTime;
endDate: DateTime;
location: Location;
}
export interface CalendarRangeEdit {
startDate?: DateTime;
endDate?: DateTime;
location?: Location;
startDate?: DateTime;
endDate?: DateTime;
location?: Location;
}
export interface Calendar {
id: number;
id: number;
}
export interface CalendarLight {
id: number;
endDate: DateTime;
startDate: DateTime;
mainUser: User;
persons: Person[];
status: "valid" | "moved" | "canceled";
id: number;
endDate: DateTime;
startDate: DateTime;
mainUser: User;
persons: Person[];
status: "valid" | "moved" | "canceled";
}
export interface CalendarRemote {
id: number;
endDate: DateTime;
startDate: DateTime;
title: string;
isAllDay: boolean;
id: number;
endDate: DateTime;
startDate: DateTime;
title: string;
isAllDay: boolean;
}
export type EventInputCalendarRange = EventInput & {
id: string;
userId: number;
userLabel: string;
calendarRangeId: number;
locationId: number;
locationName: string;
start: string;
end: string;
is: "range";
id: string;
userId: number;
userLabel: string;
calendarRangeId: number;
locationId: number;
locationName: string;
start: string;
end: string;
is: "range";
};
export function isEventInputCalendarRange(
toBeDetermined: EventInputCalendarRange | EventInput,
toBeDetermined: EventInputCalendarRange | EventInput,
): toBeDetermined is EventInputCalendarRange {
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
return (
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
);
}
export {};

View File

@@ -1,105 +1,119 @@
<template>
<div :style="style" class="calendar-active">
<span class="badge-user">
{{ user.text }}
<template v-if="invite !== null">
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
<i v-else-if="invite.status === 'declined'" class="fa fa-times" />
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" />
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" />
<span v-else="">{{ invite.status }}</span>
</template>
</span>
<span class="form-check-inline form-switch">
<input
class="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
v-model="rangeShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Disponibilités"
><i class="fa fa-calendar-check-o"
/></label>
</span>
<span class="form-check-inline form-switch">
<input
class="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
v-model="remoteShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Agenda"
><i class="fa fa-calendar"
/></label>
</span>
</div>
<div :style="style" class="calendar-active">
<span class="badge-user">
{{ user.text }}
<template v-if="invite !== null">
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
<i
v-else-if="invite.status === 'declined'"
class="fa fa-times"
/>
<i
v-else-if="invite.status === 'pending'"
class="fa fa-question-o"
/>
<i
v-else-if="invite.status === 'tentative'"
class="fa fa-question"
/>
<span v-else="">{{ invite.status }}</span>
</template>
</span>
<span class="form-check-inline form-switch">
<input
class="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
v-model="rangeShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Disponibilités"
><i class="fa fa-calendar-check-o"
/></label>
</span>
<span class="form-check-inline form-switch">
<input
class="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
v-model="remoteShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Agenda"
><i class="fa fa-calendar"
/></label>
</span>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "CalendarActive",
props: {
user: {
type: Object,
required: true,
name: "CalendarActive",
props: {
user: {
type: Object,
required: true,
},
invite: {
type: Object,
required: false,
default: null,
},
},
invite: {
type: Object,
required: false,
default: null,
computed: {
style() {
return {
backgroundColor: this.$store.getters.getUserData(this.user)
.mainColor,
};
},
rangeShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
ranges: value,
});
},
get() {
return this.$store.getters.isRangeShownOnCalendarForUser(
this.user,
);
},
},
remoteShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
remotes: value,
});
},
get() {
return this.$store.getters.isRemoteShownOnCalendarForUser(
this.user,
);
},
},
},
},
computed: {
style() {
return {
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
};
},
rangeShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
ranges: value,
});
},
get() {
return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
},
},
remoteShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
remotes: value,
});
},
get() {
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
},
},
},
};
</script>
<style scoped lang="scss">
.calendar-active {
margin: 0 0.25rem 0.25rem 0;
padding: 0.5rem;
margin: 0 0.25rem 0.25rem 0;
padding: 0.5rem;
border-radius: 0.5rem;
border-radius: 0.5rem;
color: var(--bs-blue);
color: var(--bs-blue);
& > .badge-user {
margin-right: 0.5rem;
}
& > .badge-user {
margin-right: 0.5rem;
}
}
</style>

View File

@@ -14,37 +14,37 @@ export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/
* @return Promise
*/
export const fetchCalendarRangeForUser = (
user: User,
start: Date,
end: Date,
user: User,
start: Date,
end: Date,
): Promise<CalendarRange[]> => {
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
};
export const fetchCalendarRemoteForUser = (
user: User,
start: Date,
end: Date,
user: User,
start: Date,
end: Date,
): Promise<CalendarRemote[]> => {
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
};
export const fetchCalendarLocalForUser = (
user: User,
start: Date,
end: Date,
user: User,
start: Date,
end: Date,
): Promise<CalendarLight[]> => {
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
};

View File

@@ -1,17 +1,17 @@
const COLORS = [
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
];
export { COLORS };

View File

@@ -1,117 +1,117 @@
import { COLORS } from "../const";
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import {
DateTime,
User,
DateTime,
User,
} from "../../../../../../ChillMainBundle/Resources/public/types";
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
import type { EventInputCalendarRange } from "../../../types";
import { EventInput } from "@fullcalendar/core";
export interface UserData {
user: User;
calendarRanges: CalendarRange[];
calendarRangesLoaded: {}[];
remotes: CalendarRemote[];
remotesLoaded: {}[];
locals: CalendarRemote[];
localsLoaded: {}[];
mainColor: string;
user: User;
calendarRanges: CalendarRange[];
calendarRangesLoaded: {}[];
remotes: CalendarRemote[];
remotesLoaded: {}[];
locals: CalendarRemote[];
localsLoaded: {}[];
mainColor: string;
}
export const addIdToValue = (string: string, id: number): string => {
const array = string ? string.split(",") : [];
array.push(id.toString());
const str = array.join();
return str;
const array = string ? string.split(",") : [];
array.push(id.toString());
const str = array.join();
return str;
};
export const removeIdFromValue = (string: string, id: number) => {
let array = string.split(",");
array = array.filter((el) => el !== id.toString());
const str = array.join();
return str;
let array = string.split(",");
array = array.filter((el) => el !== id.toString());
const str = array.join();
return str;
};
/*
* Assign missing keys for the ConcernedGroups component
*/
export const mapEntity = (entity: EventInput): EventInput => {
const calendar = { ...entity };
Object.assign(calendar, { thirdParties: entity.professionals });
const calendar = { ...entity };
Object.assign(calendar, { thirdParties: entity.professionals });
if (entity.startDate !== null) {
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
}
if (entity.endDate !== null) {
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
}
if (entity.startDate !== null) {
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
}
if (entity.endDate !== null) {
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
}
if (entity.calendarRange !== null) {
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
}
if (entity.calendarRange !== null) {
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
}
return calendar;
return calendar;
};
export const createUserData = (user: User, colorIndex: number): UserData => {
const colorId = colorIndex % COLORS.length;
const colorId = colorIndex % COLORS.length;
return {
user: user,
calendarRanges: [],
calendarRangesLoaded: [],
remotes: [],
remotesLoaded: [],
locals: [],
localsLoaded: [],
mainColor: COLORS[colorId],
};
return {
user: user,
calendarRanges: [],
calendarRangesLoaded: [],
remotes: [],
remotesLoaded: [],
locals: [],
localsLoaded: [],
mainColor: COLORS[colorId],
};
};
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
export const calendarRangeToFullCalendarEvent = (
entity: CalendarRange,
entity: CalendarRange,
): EventInputCalendarRange => {
return {
id: `range_${entity.id}`,
title: "(" + entity.user.text + ")",
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: false,
userId: entity.user.id,
userLabel: entity.user.label,
calendarRangeId: entity.id,
locationId: entity.location.id,
locationName: entity.location.name,
is: "range",
};
return {
id: `range_${entity.id}`,
title: "(" + entity.user.text + ")",
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: false,
userId: entity.user.id,
userLabel: entity.user.label,
calendarRangeId: entity.id,
locationId: entity.location.id,
locationName: entity.location.name,
is: "range",
};
};
export const remoteToFullCalendarEvent = (
entity: CalendarRemote,
entity: CalendarRemote,
): EventInput & { id: string } => {
return {
id: `range_${entity.id}`,
title: entity.title,
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: entity.isAllDay,
is: "remote",
};
return {
id: `range_${entity.id}`,
title: entity.title,
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: entity.isAllDay,
is: "remote",
};
};
export const localsToFullCalendarEvent = (
entity: CalendarLight,
entity: CalendarLight,
): EventInput & { id: string; originId: number } => {
return {
id: `local_${entity.id}`,
title: entity.persons.map((p) => p.text).join(", "),
originId: entity.id,
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: false,
is: "local",
};
return {
id: `local_${entity.id}`,
title: entity.persons.map((p) => p.text).join(", "),
originId: entity.id,
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: false,
is: "local",
};
};

View File

@@ -1,50 +1,58 @@
<template>
<div class="btn-group" role="group">
<button
id="btnGroupDrop1"
type="button"
class="btn btn-misc dropdown-toggle"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<template v-if="status === Statuses.PENDING">
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
</template>
<template v-else-if="status === Statuses.ACCEPTED">
<span class="fa fa-check"></span> {{ $t("Accepted") }}
</template>
<template v-else-if="status === Statuses.DECLINED">
<span class="fa fa-times"></span> {{ $t("Declined") }}
</template>
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
<span class="fa fa-question"></span> {{ $t("Tentative") }}
</template>
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<li v-if="status !== Statuses.ACCEPTED">
<a class="dropdown-item" @click="changeStatus(Statuses.ACCEPTED)"
><i class="fa fa-check" aria-hidden="true"></i> {{ $t("Accept") }}</a
<div class="btn-group" role="group">
<button
id="btnGroupDrop1"
type="button"
class="btn btn-misc dropdown-toggle"
data-bs-toggle="dropdown"
aria-expanded="false"
>
</li>
<li v-if="status !== Statuses.DECLINED">
<a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)"
><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a
>
</li>
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a
>
</li>
<li v-if="status !== Statuses.PENDING">
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a
>
</li>
</ul>
</div>
<template v-if="status === Statuses.PENDING">
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
</template>
<template v-else-if="status === Statuses.ACCEPTED">
<span class="fa fa-check"></span> {{ $t("Accepted") }}
</template>
<template v-else-if="status === Statuses.DECLINED">
<span class="fa fa-times"></span> {{ $t("Declined") }}
</template>
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
<span class="fa fa-question"></span> {{ $t("Tentative") }}
</template>
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<li v-if="status !== Statuses.ACCEPTED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.ACCEPTED)"
><i class="fa fa-check" aria-hidden="true"></i>
{{ $t("Accept") }}</a
>
</li>
<li v-if="status !== Statuses.DECLINED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.DECLINED)"
><i class="fa fa-times" aria-hidden="true"></i>
{{ $t("Decline") }}</a
>
</li>
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
><i class="fa fa-question"></i>
{{ $t("Tentatively_accept") }}</a
>
</li>
<li v-if="status !== Statuses.PENDING">
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
><i class="fa fa-hourglass-o"></i>
{{ $t("Set_pending") }}</a
>
</li>
</ul>
</div>
</template>
<script lang="ts">
@@ -56,67 +64,69 @@ const PENDING = "pending";
const TENTATIVELY_ACCEPTED = "tentative";
const i18n = {
messages: {
fr: {
Give_an_answer: "Répondre",
Accepted: "Accepté",
Declined: "Refusé",
Tentative: "Accepté provisoirement",
Accept: "Accepter",
Decline: "Refuser",
Tentatively_accept: "Accepter provisoirement",
Set_pending: "Ne pas répondre",
messages: {
fr: {
Give_an_answer: "Répondre",
Accepted: "Accepté",
Declined: "Refusé",
Tentative: "Accepté provisoirement",
Accept: "Accepter",
Decline: "Refuser",
Tentatively_accept: "Accepter provisoirement",
Set_pending: "Ne pas répondre",
},
},
},
};
export default defineComponent({
name: "Answer",
i18n,
props: {
calendarId: { type: Number, required: true },
status: {
type: String as PropType<
"accepted" | "declined" | "pending" | "tentative"
>,
required: true,
name: "Answer",
i18n,
props: {
calendarId: { type: Number, required: true },
status: {
type: String as PropType<
"accepted" | "declined" | "pending" | "tentative"
>,
required: true,
},
},
},
emits: {
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") {
return true;
emits: {
statusChanged(
payload: "accepted" | "declined" | "pending" | "tentative",
) {
return true;
},
},
},
data() {
return {
Statuses: {
ACCEPTED,
DECLINED,
PENDING,
TENTATIVELY_ACCEPTED,
},
};
},
methods: {
changeStatus: function (
newStatus: "accepted" | "declined" | "pending" | "tentative",
) {
console.log("changeStatus", newStatus);
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
window
.fetch(url, {
method: "POST",
})
.then((r: Response) => {
if (!r.ok) {
console.error("could not confirm answer", newStatus);
return;
}
console.log("answer sent", newStatus);
this.$emit("statusChanged", newStatus);
});
data() {
return {
Statuses: {
ACCEPTED,
DECLINED,
PENDING,
TENTATIVELY_ACCEPTED,
},
};
},
methods: {
changeStatus: function (
newStatus: "accepted" | "declined" | "pending" | "tentative",
) {
console.log("changeStatus", newStatus);
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
window
.fetch(url, {
method: "POST",
})
.then((r: Response) => {
if (!r.ok) {
console.error("could not confirm answer", newStatus);
return;
}
console.log("answer sent", newStatus);
this.$emit("statusChanged", newStatus);
});
},
},
},
});
</script>

View File

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

View File

@@ -1,28 +1,28 @@
<template>
<component :is="Teleport" to="body">
<modal v-if="showModal" @close="closeModal">
<template v-slot:header>
<h3>{{ "Modifier le lieu" }}</h3>
</template>
<component :is="Teleport" to="body">
<modal v-if="showModal" @close="closeModal">
<template v-slot:header>
<h3>{{ "Modifier le lieu" }}</h3>
</template>
<template v-slot:body>
<div></div>
<label>Localisation</label>
<vue-multiselect
v-model="location"
:options="locations"
:label="'name'"
:track-by="'id'"
></vue-multiselect>
</template>
<template v-slot:body>
<div></div>
<label>Localisation</label>
<vue-multiselect
v-model="location"
:options="locations"
:label="'name'"
:track-by="'id'"
></vue-multiselect>
</template>
<template v-slot:footer>
<button class="btn btn-save" @click="saveAndClose">
{{ "Enregistrer" }}
</button>
</template>
</modal>
</component>
<template v-slot:footer>
<button class="btn btn-save" @click="saveAndClose">
{{ "Enregistrer" }}
</button>
</template>
</modal>
</component>
</template>
<script setup lang="ts">
@@ -39,7 +39,7 @@ import VueMultiselect from "vue-multiselect";
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
const Teleport = teleport_ as new () => {
$props: VNodeProps & TeleportProps;
$props: VNodeProps & TeleportProps;
};
const store = useStore(key);
@@ -50,37 +50,37 @@ const showModal = ref(false);
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
const locations = computed<Location[]>(() => {
return store.state.locations.locations;
return store.state.locations.locations;
});
const startEdit = function (event: EventApi): void {
console.log("startEditing", event);
calendarRangeId.value = event.extendedProps.calendarRangeId;
location.value =
store.getters["locations/getLocationById"](
event.extendedProps.locationId,
) || null;
console.log("startEditing", event);
calendarRangeId.value = event.extendedProps.calendarRangeId;
location.value =
store.getters["locations/getLocationById"](
event.extendedProps.locationId,
) || null;
console.log("new location value", location.value);
console.log("calendar range id", calendarRangeId.value);
showModal.value = true;
console.log("new location value", location.value);
console.log("calendar range id", calendarRangeId.value);
showModal.value = true;
};
const saveAndClose = function (e: Event): void {
console.log("saveEditAndClose", e);
console.log("saveEditAndClose", e);
store
.dispatch("calendarRanges/patchRangeLocation", {
location: location.value,
calendarRangeId: calendarRangeId.value,
})
.then((_) => {
showModal.value = false;
});
store
.dispatch("calendarRanges/patchRangeLocation", {
location: location.value,
calendarRangeId: calendarRangeId.value,
})
.then((_) => {
showModal.value = false;
});
};
const closeModal = function (_: any): void {
showModal.value = false;
showModal.value = false;
};
defineExpose({ startEdit });

View File

@@ -1,27 +1,27 @@
const appMessages = {
fr: {
created_availabilities: "Lieu des plages de disponibilités créées",
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
show_my_calendar: "Afficher mon calendrier",
show_weekends: "Afficher les week-ends",
copy_range: "Copier",
copy_range_from_to: "Copier les plages",
from_day_to_day: "d'un jour à l'autre",
from_week_to_week: "d'une semaine à l'autre",
copy_range_how_to:
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
new_range_to_save: "Nouvelles plages à enregistrer",
update_range_to_save: "Plages à modifier",
delete_range_to_save: "Plages à supprimer",
by: "Par",
main_user_concerned: "Utilisateur concerné",
dateFrom: "De",
dateTo: "à",
day: "Jour",
week: "Semaine",
month: "Mois",
today: "Aujourd'hui",
},
fr: {
created_availabilities: "Lieu des plages de disponibilités créées",
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
show_my_calendar: "Afficher mon calendrier",
show_weekends: "Afficher les week-ends",
copy_range: "Copier",
copy_range_from_to: "Copier les plages",
from_day_to_day: "d'un jour à l'autre",
from_week_to_week: "d'une semaine à l'autre",
copy_range_how_to:
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
new_range_to_save: "Nouvelles plages à enregistrer",
update_range_to_save: "Plages à modifier",
delete_range_to_save: "Plages à supprimer",
by: "Par",
main_user_concerned: "Utilisateur concerné",
dateFrom: "De",
dateTo: "à",
day: "Jour",
week: "Semaine",
month: "Mois",
today: "Aujourd'hui",
},
};
export { appMessages };

View File

@@ -7,13 +7,13 @@ import App2 from "./App2.vue";
import { useI18n } from "vue-i18n";
futureStore().then((store) => {
const i18n = _createI18n(appMessages, false);
const i18n = _createI18n(appMessages, false);
const app = createApp({
template: `<app></app>`,
})
.use(store, key)
.use(i18n)
.component("app", App2)
.mount("#myCalendar");
const app = createApp({
template: `<app></app>`,
})
.use(store, key)
.use(i18n)
.component("app", App2)
.mount("#myCalendar");
});

View File

@@ -5,7 +5,7 @@ import me, { MeState } from "./modules/me";
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
import calendarRemotes, {
CalendarRemotesState,
CalendarRemotesState,
} from "./modules/calendarRemotes";
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
@@ -15,40 +15,42 @@ import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
const debug = process.env.NODE_ENV !== "production";
export interface State {
calendarRanges: CalendarRangesState;
calendarRemotes: CalendarRemotesState;
calendarLocals: CalendarLocalsState;
fullCalendar: FullCalendarState;
me: MeState;
locations: LocationState;
calendarRanges: CalendarRangesState;
calendarRemotes: CalendarRemotesState;
calendarLocals: CalendarLocalsState;
fullCalendar: FullCalendarState;
me: MeState;
locations: LocationState;
}
export const key: InjectionKey<Store<State>> = Symbol();
const futureStore = function (): Promise<Store<State>> {
return whoami().then((user: User) => {
const store = createStore<State>({
strict: debug,
modules: {
me,
fullCalendar,
calendarRanges,
calendarRemotes,
calendarLocals,
locations,
},
mutations: {},
});
return whoami().then((user: User) => {
const store = createStore<State>({
strict: debug,
modules: {
me,
fullCalendar,
calendarRanges,
calendarRemotes,
calendarLocals,
locations,
},
mutations: {},
});
store.commit("me/setWhoAmi", user, { root: true });
store.dispatch("locations/getLocations", null, { root: true }).then((_) => {
return store.dispatch("locations/getCurrentLocation", null, {
root: true,
});
});
store.commit("me/setWhoAmi", user, { root: true });
store
.dispatch("locations/getLocations", null, { root: true })
.then((_) => {
return store.dispatch("locations/getCurrentLocation", null, {
root: true,
});
});
return Promise.resolve(store);
});
return Promise.resolve(store);
});
};
export default futureStore;

View File

@@ -8,99 +8,109 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
import { COLORS } from "../../../Calendar/const";
export interface CalendarLocalsState {
locals: EventInput[];
localsLoaded: { start: number; end: number }[];
localsIndex: Set<string>;
key: number;
locals: EventInput[];
localsLoaded: { start: number; end: number }[];
localsIndex: Set<string>;
key: number;
}
type Context = ActionContext<CalendarLocalsState, State>;
export default {
namespaced: true,
state: (): CalendarLocalsState => ({
locals: [],
localsLoaded: [],
localsIndex: new Set<string>(),
key: 0,
}),
getters: {
isLocalsLoaded:
(state: CalendarLocalsState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.localsLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) {
return true;
}
}
namespaced: true,
state: (): CalendarLocalsState => ({
locals: [],
localsLoaded: [],
localsIndex: new Set<string>(),
key: 0,
}),
getters: {
isLocalsLoaded:
(state: CalendarLocalsState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.localsLoaded) {
if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true;
}
}
return false;
},
},
mutations: {
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
console.log("addLocals", ranges);
const toAdd = ranges
.map((cr) => localsToFullCalendarEvent(cr))
.filter((r) => !state.localsIndex.has(r.id));
toAdd.forEach((r) => {
state.localsIndex.add(r.id);
state.locals.push(r);
});
state.key = state.key + toAdd.length;
return false;
},
},
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) {
state.localsLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
mutations: {
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
console.log("addLocals", ranges);
const toAdd = ranges
.map((cr) => localsToFullCalendarEvent(cr))
.filter((r) => !state.localsIndex.has(r.id));
toAdd.forEach((r) => {
state.localsIndex.add(r.id);
state.locals.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarLocalsState,
payload: { start: Date; end: Date },
) {
state.localsLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
},
},
actions: {
fetchLocals(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
actions: {
fetchLocals(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null);
}
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null);
}
if (ctx.getters.isLocalsLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isLocalsLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end)
.then((remotes: CalendarLight[]) => {
// to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes);
const inputs = remotes
.map((cr) => localsToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return fetchCalendarLocalForUser(
ctx.rootGetters["me/getMe"],
start,
end,
)
.then((remotes: CalendarLight[]) => {
// to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes);
const inputs = remotes
.map((cr) => localsToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return Promise.resolve(null);
});
return Promise.resolve(null);
});
},
},
},
} as Module<CalendarLocalsState, State>;

View File

@@ -1,10 +1,10 @@
import { State } from "./../index";
import { ActionContext, Module } from "vuex";
import {
CalendarRange,
CalendarRangeCreate,
CalendarRangeEdit,
isEventInputCalendarRange,
CalendarRange,
CalendarRangeCreate,
CalendarRangeEdit,
isEventInputCalendarRange,
} from "../../../../types";
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
@@ -12,332 +12,369 @@ import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils"
import { EventInput } from "@fullcalendar/core";
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import {
datetimeToISO,
dateToISO,
ISOToDatetime,
datetimeToISO,
dateToISO,
ISOToDatetime,
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import type { EventInputCalendarRange } from "../../../../types";
export interface CalendarRangesState {
ranges: (EventInput | EventInputCalendarRange)[];
rangesLoaded: { start: number; end: number }[];
rangesIndex: Set<string>;
key: number;
ranges: (EventInput | EventInputCalendarRange)[];
rangesLoaded: { start: number; end: number }[];
rangesIndex: Set<string>;
key: number;
}
type Context = ActionContext<CalendarRangesState, State>;
export default {
namespaced: true,
state: (): CalendarRangesState => ({
ranges: [],
rangesLoaded: [],
rangesIndex: new Set<string>(),
key: 0,
}),
getters: {
isRangeLoaded:
(state: CalendarRangesState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.rangesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) {
return true;
}
}
namespaced: true,
state: (): CalendarRangesState => ({
ranges: [],
rangesLoaded: [],
rangesIndex: new Set<string>(),
key: 0,
}),
getters: {
isRangeLoaded:
(state: CalendarRangesState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.rangesLoaded) {
if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true;
}
}
return false;
},
getRangesOnDate:
(state: CalendarRangesState) =>
(date: Date): EventInputCalendarRange[] => {
const founds = [];
const dateStr = dateToISO(date) as string;
return false;
},
getRangesOnDate:
(state: CalendarRangesState) =>
(date: Date): EventInputCalendarRange[] => {
const founds = [];
const dateStr = dateToISO(date) as string;
for (const range of state.ranges) {
if (
isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr)
) {
founds.push(range);
}
}
for (const range of state.ranges) {
if (
isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr)
) {
founds.push(range);
}
}
return founds;
},
getRangesOnWeek:
(state: CalendarRangesState) =>
(mondayDate: Date): EventInputCalendarRange[] => {
const founds = [];
for (const d of Array.from(Array(7).keys())) {
const dateOfWeek = new Date(mondayDate);
dateOfWeek.setDate(mondayDate.getDate() + d);
const dateStr = dateToISO(dateOfWeek) as string;
for (const range of state.ranges) {
if (
isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr)
) {
founds.push(range);
return founds;
},
getRangesOnWeek:
(state: CalendarRangesState) =>
(mondayDate: Date): EventInputCalendarRange[] => {
const founds = [];
for (const d of Array.from(Array(7).keys())) {
const dateOfWeek = new Date(mondayDate);
dateOfWeek.setDate(mondayDate.getDate() + d);
const dateStr = dateToISO(dateOfWeek) as string;
for (const range of state.ranges) {
if (
isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr)
) {
founds.push(range);
}
}
}
return founds;
},
},
mutations: {
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
const toAdd = ranges
.map((cr) => calendarRangeToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
}))
.filter((r) => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addExternals(
state: CalendarRangesState,
externalEvents: (EventInput & { id: string })[],
) {
const toAdd = externalEvents.filter(
(r) => !state.rangesIndex.has(r.id),
);
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarRangesState,
payload: { start: Date; end: Date },
) {
state.rangesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
addRange(state: CalendarRangesState, payload: CalendarRange) {
const asEvent = calendarRangeToFullCalendarEvent(payload);
state.ranges.push({
...asEvent,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
});
state.rangesIndex.add(asEvent.id);
state.key = state.key + 1;
},
removeRange(state: CalendarRangesState, calendarRangeId: number) {
const found = state.ranges.find(
(r) =>
r.calendarRangeId === calendarRangeId && r.is === "range",
);
if (found !== undefined) {
state.ranges = state.ranges.filter(
(r) =>
!(
r.calendarRangeId === calendarRangeId &&
r.is === "range"
),
);
if (typeof found.id === "string") {
// should always be true
state.rangesIndex.delete(found.id);
}
state.key = state.key + 1;
}
}
}
return founds;
},
},
mutations: {
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
const toAdd = ranges
.map((cr) => calendarRangeToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
}))
.filter((r) => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addExternals(
state: CalendarRangesState,
externalEvents: (EventInput & { id: string })[],
) {
const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) {
state.rangesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
addRange(state: CalendarRangesState, payload: CalendarRange) {
const asEvent = calendarRangeToFullCalendarEvent(payload);
state.ranges.push({
...asEvent,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
});
state.rangesIndex.add(asEvent.id);
state.key = state.key + 1;
},
removeRange(state: CalendarRangesState, calendarRangeId: number) {
const found = state.ranges.find(
(r) => r.calendarRangeId === calendarRangeId && r.is === "range",
);
if (found !== undefined) {
state.ranges = state.ranges.filter(
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"),
);
if (typeof found.id === "string") {
// should always be true
state.rangesIndex.delete(found.id);
}
state.key = state.key + 1;
}
},
updateRange(state: CalendarRangesState, range: CalendarRange) {
const found = state.ranges.find(
(r) => r.calendarRangeId === range.id && r.is === "range",
);
const newEvent = calendarRangeToFullCalendarEvent(range);
if (found !== undefined) {
found.start = newEvent.start;
found.end = newEvent.end;
found.locationId = range.location.id;
found.locationName = range.location.name;
}
state.key = state.key + 1;
},
},
actions: {
fetchRanges(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRangeLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarRangeForUser(
ctx.rootGetters["me/getMe"],
start,
end,
).then((ranges: CalendarRange[]) => {
ctx.commit("addRanges", ranges);
return Promise.resolve(null);
});
},
createRange(
ctx: Context,
{ start, end, location }: { start: Date; end: Date; location: Location },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range.json?`;
if (ctx.rootState.me.me === null) {
throw new Error("user is currently null");
}
const body = {
user: {
id: ctx.rootState.me.me.id,
type: "user",
},
startDate: {
datetime: datetimeToISO(start),
updateRange(state: CalendarRangesState, range: CalendarRange) {
const found = state.ranges.find(
(r) => r.calendarRangeId === range.id && r.is === "range",
);
const newEvent = calendarRangeToFullCalendarEvent(range);
if (found !== undefined) {
found.start = newEvent.start;
found.end = newEvent.end;
found.locationId = range.location.id;
found.locationName = range.location.name;
}
state.key = state.key + 1;
},
endDate: {
datetime: datetimeToISO(end),
},
actions: {
fetchRanges(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRangeLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarRangeForUser(
ctx.rootGetters["me/getMe"],
start,
end,
).then((ranges: CalendarRange[]) => {
ctx.commit("addRanges", ranges);
return Promise.resolve(null);
});
},
location: {
id: location.id,
type: "location",
createRange(
ctx: Context,
{
start,
end,
location,
}: { start: Date; end: Date; location: Location },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range.json?`;
if (ctx.rootState.me.me === null) {
throw new Error("user is currently null");
}
const body = {
user: {
id: ctx.rootState.me.me.id,
type: "user",
},
startDate: {
datetime: datetimeToISO(start),
},
endDate: {
datetime: datetimeToISO(end),
},
location: {
id: location.id,
type: "location",
},
} as CalendarRangeCreate;
return makeFetch<CalendarRangeCreate, CalendarRange>(
"POST",
url,
body,
)
.then((newRange) => {
ctx.commit("addRange", newRange);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
throw error;
});
},
} as CalendarRangeCreate;
deleteRange(ctx: Context, calendarRangeId: number) {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body)
.then((newRange) => {
ctx.commit("addRange", newRange);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
throw error;
});
},
deleteRange(ctx: Context, calendarRangeId: number) {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
makeFetch<undefined, never>("DELETE", url).then(() => {
ctx.commit("removeRange", calendarRangeId);
});
},
patchRangeTime(
ctx,
{
calendarRangeId,
start,
end,
}: { calendarRangeId: number; start: Date; end: Date },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
startDate: {
datetime: datetimeToISO(start),
makeFetch<undefined, never>("DELETE", url).then(() => {
ctx.commit("removeRange", calendarRangeId);
});
},
endDate: {
datetime: datetimeToISO(end),
patchRangeTime(
ctx,
{
calendarRangeId,
start,
end,
}: { calendarRangeId: number; start: Date; end: Date },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
startDate: {
datetime: datetimeToISO(start),
},
endDate: {
datetime: datetimeToISO(end),
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>(
"PATCH",
url,
body,
)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
},
} as CalendarRangeEdit;
patchRangeLocation(
ctx,
{
location,
calendarRangeId,
}: { location: Location; calendarRangeId: number },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
location: {
id: location.id,
type: "location",
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
},
patchRangeLocation(
ctx,
{
location,
calendarRangeId,
}: { location: Location; calendarRangeId: number },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
location: {
id: location.id,
type: "location",
return makeFetch<CalendarRangeEdit, CalendarRange>(
"PATCH",
url,
body,
)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
},
} as CalendarRangeEdit;
copyFromDayToAnotherDay(
ctx,
{ from, to }: { from: Date; to: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnDate"](from);
const promises = [];
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
start.setFullYear(
to.getFullYear(),
to.getMonth(),
to.getDate(),
);
const end = new Date(ISOToDatetime(r.end) as Date);
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(
ctx.dispatch("createRange", { start, end, location }),
);
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
copyFromWeekToAnotherWeek(
ctx: Context,
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnWeek"](fromMonday);
const promises = [];
const diffTime = toMonday.getTime() - fromMonday.getTime();
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
const end = new Date(ISOToDatetime(r.end) as Date);
start.setTime(start.getTime() + diffTime);
end.setTime(end.getTime() + diffTime);
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(
ctx.dispatch("createRange", { start, end, location }),
);
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
},
copyFromDayToAnotherDay(
ctx,
{ from, to }: { from: Date; to: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnDate"](from);
const promises = [];
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const end = new Date(ISOToDatetime(r.end) as Date);
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(ctx.dispatch("createRange", { start, end, location }));
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
copyFromWeekToAnotherWeek(
ctx: Context,
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnWeek"](fromMonday);
const promises = [];
const diffTime = toMonday.getTime() - fromMonday.getTime();
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
const end = new Date(ISOToDatetime(r.end) as Date);
start.setTime(start.getTime() + diffTime);
end.setTime(end.getTime() + diffTime);
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(ctx.dispatch("createRange", { start, end, location }));
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
},
} as Module<CalendarRangesState, State>;

View File

@@ -8,102 +8,109 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
import { COLORS } from "../../../Calendar/const";
export interface CalendarRemotesState {
remotes: EventInput[];
remotesLoaded: { start: number; end: number }[];
remotesIndex: Set<string>;
key: number;
remotes: EventInput[];
remotesLoaded: { start: number; end: number }[];
remotesIndex: Set<string>;
key: number;
}
type Context = ActionContext<CalendarRemotesState, State>;
export default {
namespaced: true,
state: (): CalendarRemotesState => ({
remotes: [],
remotesLoaded: [],
remotesIndex: new Set<string>(),
key: 0,
}),
getters: {
isRemotesLoaded:
(state: CalendarRemotesState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.remotesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) {
return true;
}
}
namespaced: true,
state: (): CalendarRemotesState => ({
remotes: [],
remotesLoaded: [],
remotesIndex: new Set<string>(),
key: 0,
}),
getters: {
isRemotesLoaded:
(state: CalendarRemotesState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.remotesLoaded) {
if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true;
}
}
return false;
},
},
mutations: {
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
console.log("addRemotes", ranges);
const toAdd = ranges
.map((cr) => remoteToFullCalendarEvent(cr))
.filter((r) => !state.remotesIndex.has(r.id));
toAdd.forEach((r) => {
state.remotesIndex.add(r.id);
state.remotes.push(r);
});
state.key = state.key + toAdd.length;
return false;
},
},
addLoaded(
state: CalendarRemotesState,
payload: { start: Date; end: Date },
) {
state.remotesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
mutations: {
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
console.log("addRemotes", ranges);
const toAdd = ranges
.map((cr) => remoteToFullCalendarEvent(cr))
.filter((r) => !state.remotesIndex.has(r.id));
toAdd.forEach((r) => {
state.remotesIndex.add(r.id);
state.remotes.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarRemotesState,
payload: { start: Date; end: Date },
) {
state.remotesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
},
},
actions: {
fetchRemotes(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
actions: {
fetchRemotes(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null);
}
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null);
}
if (ctx.getters.isRemotesLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRemotesLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end)
.then((remotes: CalendarRemote[]) => {
// to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes);
const inputs = remotes
.map((cr) => remoteToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return fetchCalendarRemoteForUser(
ctx.rootGetters["me/getMe"],
start,
end,
)
.then((remotes: CalendarRemote[]) => {
// to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes);
const inputs = remotes
.map((cr) => remoteToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return Promise.resolve(null);
});
return Promise.resolve(null);
});
},
},
},
} as Module<CalendarRemotesState, State>;

View File

@@ -2,77 +2,77 @@ import { State } from "./../index";
import { ActionContext } from "vuex";
export interface FullCalendarState {
currentView: {
start: Date | null;
end: Date | null;
};
key: number;
currentView: {
start: Date | null;
end: Date | null;
};
key: number;
}
type Context = ActionContext<FullCalendarState, State>;
export default {
namespaced: true,
state: (): FullCalendarState => ({
currentView: {
start: null,
end: null,
namespaced: true,
state: (): FullCalendarState => ({
currentView: {
start: null,
end: null,
},
key: 0,
}),
mutations: {
setCurrentDatesView: function (
state: FullCalendarState,
payload: { start: Date; end: Date },
): void {
state.currentView.start = payload.start;
state.currentView.end = payload.end;
},
increaseKey: function (state: FullCalendarState): void {
state.key = state.key + 1;
},
},
key: 0,
}),
mutations: {
setCurrentDatesView: function (
state: FullCalendarState,
payload: { start: Date; end: Date },
): void {
state.currentView.start = payload.start;
state.currentView.end = payload.end;
},
increaseKey: function (state: FullCalendarState): void {
state.key = state.key + 1;
},
},
actions: {
setCurrentDatesView(
ctx: Context,
{ start, end }: { start: Date | null; end: Date | null },
): Promise<null> {
console.log("dispatch setCurrentDatesView", { start, end });
actions: {
setCurrentDatesView(
ctx: Context,
{ start, end }: { start: Date | null; end: Date | null },
): Promise<null> {
console.log("dispatch setCurrentDatesView", { start, end });
if (
ctx.state.currentView.start !== start ||
ctx.state.currentView.end !== end
) {
ctx.commit("setCurrentDatesView", { start, end });
}
if (
ctx.state.currentView.start !== start ||
ctx.state.currentView.end !== end
) {
ctx.commit("setCurrentDatesView", { start, end });
}
if (start !== null && end !== null) {
return Promise.all([
ctx
.dispatch(
"calendarRanges/fetchRanges",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
ctx
.dispatch(
"calendarRemotes/fetchRemotes",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
ctx
.dispatch(
"calendarLocals/fetchLocals",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
]).then((_) => Promise.resolve(null));
} else {
return Promise.resolve(null);
}
if (start !== null && end !== null) {
return Promise.all([
ctx
.dispatch(
"calendarRanges/fetchRanges",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
ctx
.dispatch(
"calendarRemotes/fetchRemotes",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
ctx
.dispatch(
"calendarLocals/fetchLocals",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
]).then((_) => Promise.resolve(null));
} else {
return Promise.resolve(null);
}
},
},
},
};

View File

@@ -5,61 +5,61 @@ import { getLocations } from "../../../../../../../ChillMainBundle/Resources/pub
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
export interface LocationState {
locations: Location[];
locationPicked: Location | null;
currentLocation: Location | null;
locations: Location[];
locationPicked: Location | null;
currentLocation: Location | null;
}
export default {
namespaced: true,
state: (): LocationState => {
return {
locations: [],
locationPicked: null,
currentLocation: null,
};
},
getters: {
getLocationById:
(state) =>
(id: number): Location | undefined => {
return state.locations.find((l) => l.id === id);
},
},
mutations: {
setLocations(state, locations): void {
state.locations = locations;
namespaced: true,
state: (): LocationState => {
return {
locations: [],
locationPicked: null,
currentLocation: null,
};
},
setLocationPicked(state, location: Location | null): void {
if (null === location) {
state.locationPicked = null;
return;
}
getters: {
getLocationById:
(state) =>
(id: number): Location | undefined => {
return state.locations.find((l) => l.id === id);
},
},
mutations: {
setLocations(state, locations): void {
state.locations = locations;
},
setLocationPicked(state, location: Location | null): void {
if (null === location) {
state.locationPicked = null;
return;
}
state.locationPicked =
state.locations.find((l) => l.id === location.id) || null;
},
setCurrentLocation(state, location: Location | null): void {
if (null === location) {
state.currentLocation = null;
return;
}
state.locationPicked =
state.locations.find((l) => l.id === location.id) || null;
},
setCurrentLocation(state, location: Location | null): void {
if (null === location) {
state.currentLocation = null;
return;
}
state.currentLocation =
state.locations.find((l) => l.id === location.id) || null;
state.currentLocation =
state.locations.find((l) => l.id === location.id) || null;
},
},
},
actions: {
getLocations(ctx): Promise<void> {
return getLocations().then((locations) => {
ctx.commit("setLocations", locations);
return Promise.resolve();
});
actions: {
getLocations(ctx): Promise<void> {
return getLocations().then((locations) => {
ctx.commit("setLocations", locations);
return Promise.resolve();
});
},
getCurrentLocation(ctx): Promise<void> {
return whereami().then((location) => {
ctx.commit("setCurrentLocation", location);
});
},
},
getCurrentLocation(ctx): Promise<void> {
return whereami().then((location) => {
ctx.commit("setCurrentLocation", location);
});
},
},
} as Module<LocationState, State>;

View File

@@ -3,24 +3,24 @@ import { User } from "../../../../../../../ChillMainBundle/Resources/public/type
import { ActionContext } from "vuex";
export interface MeState {
me: User | null;
me: User | null;
}
type Context = ActionContext<MeState, State>;
export default {
namespaced: true,
state: (): MeState => ({
me: null,
}),
getters: {
getMe: function (state: MeState): User | null {
return state.me;
namespaced: true,
state: (): MeState => ({
me: null,
}),
getters: {
getMe: function (state: MeState): User | null {
return state.me;
},
},
},
mutations: {
setWhoAmi(state: MeState, me: User) {
state.me = me;
mutations: {
setWhoAmi(state: MeState, me: User) {
state.me = me;
},
},
},
};

View File

@@ -1,51 +1,51 @@
<template>
<div>
<h2 class="chill-red">
{{ $t("choose_your_calendar_user") }}
</h2>
<VueMultiselect
name="field"
id="calendarUserSelector"
v-model="value"
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_user')"
:multiple="true"
:close-on-select="false"
:allow-empty="true"
:model-value="value"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
@select="selectUsers"
@remove="unSelectUsers"
@close="coloriseSelectedValues"
:options="options"
/>
</div>
<div class="form-check">
<input
type="checkbox"
id="myCalendar"
class="form-check-input"
v-model="showMyCalendarWidget"
/>
<label class="form-check-label" for="myCalendar">{{
$t("show_my_calendar")
}}</label>
</div>
<div class="form-check">
<input
type="checkbox"
id="weekends"
class="form-check-input"
@click="toggleWeekends"
/>
<label class="form-check-label" for="weekends">{{
$t("show_weekends")
}}</label>
</div>
<div>
<h2 class="chill-red">
{{ $t("choose_your_calendar_user") }}
</h2>
<VueMultiselect
name="field"
id="calendarUserSelector"
v-model="value"
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_user')"
:multiple="true"
:close-on-select="false"
:allow-empty="true"
:model-value="value"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
@select="selectUsers"
@remove="unSelectUsers"
@close="coloriseSelectedValues"
:options="options"
/>
</div>
<div class="form-check">
<input
type="checkbox"
id="myCalendar"
class="form-check-input"
v-model="showMyCalendarWidget"
/>
<label class="form-check-label" for="myCalendar">{{
$t("show_my_calendar")
}}</label>
</div>
<div class="form-check">
<input
type="checkbox"
id="weekends"
class="form-check-input"
@click="toggleWeekends"
/>
<label class="form-check-label" for="weekends">{{
$t("show_weekends")
}}</label>
</div>
</template>
<script>
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
@@ -53,183 +53,206 @@ import VueMultiselect from "vue-multiselect";
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
const COLORS = [
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
];
export default {
name: "CalendarUserSelector",
components: { VueMultiselect },
props: [
"users",
"updateEventsSource",
"calendarEvents",
"showMyCalendar",
"toggleMyCalendar",
"toggleWeekends",
],
data() {
return {
errorMsg: [],
value: [],
options: [],
};
},
computed: {
showMyCalendarWidget: {
set(value) {
this.toggleMyCalendar(value);
this.updateEventsSource();
},
get() {
return this.showMyCalendar;
},
name: "CalendarUserSelector",
components: { VueMultiselect },
props: [
"users",
"updateEventsSource",
"calendarEvents",
"showMyCalendar",
"toggleMyCalendar",
"toggleWeekends",
],
data() {
return {
errorMsg: [],
value: [],
options: [],
};
},
},
methods: {
init() {
this.fetchData();
computed: {
showMyCalendarWidget: {
set(value) {
this.toggleMyCalendar(value);
this.updateEventsSource();
},
get() {
return this.showMyCalendar;
},
},
},
fetchData() {
fetchCalendarRanges()
.then(
(calendarRanges) =>
new Promise((resolve, reject) => {
let results = calendarRanges.results;
let users = [];
results.forEach((i) => {
if (!users.some((j) => i.user.id === j.id)) {
let ratio = Math.floor(users.length / COLORS.length);
let colorIndex = users.length - ratio * COLORS.length;
users.push({
id: i.user.id,
username: i.user.username,
color: COLORS[colorIndex],
});
}
});
let calendarEvents = [];
users.forEach((u) => {
let arr = results
.filter((i) => i.user.id === u.id)
.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
calendarRangeId: i.id,
sourceColor: u.color,
//display: 'background' // can be an option for the disponibility
}));
calendarEvents.push({
events: arr,
color: u.color,
textColor: "#444444",
editable: false,
id: u.id,
});
});
this.users.loaded = users;
this.options = users;
this.calendarEvents.loaded = calendarEvents;
whoami().then(
(me) =>
new Promise((resolve, reject) => {
this.users.logged = me;
let currentUser = users.find((u) => u.id === me.id);
this.value = currentUser;
fetchCalendar(currentUser.id).then(
(calendar) =>
methods: {
init() {
this.fetchData();
},
fetchData() {
fetchCalendarRanges()
.then(
(calendarRanges) =>
new Promise((resolve, reject) => {
let results = calendar.results;
let events = results.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
}));
let calendarEventsCurrentUser = {
events: events,
color: "darkblue",
id: 1000,
editable: false,
};
this.calendarEvents.user = calendarEventsCurrentUser;
let results = calendarRanges.results;
this.selectUsers(currentUser);
let users = [];
resolve();
results.forEach((i) => {
if (!users.some((j) => i.user.id === j.id)) {
let ratio = Math.floor(
users.length / COLORS.length,
);
let colorIndex =
users.length - ratio * COLORS.length;
users.push({
id: i.user.id,
username: i.user.username,
color: COLORS[colorIndex],
});
}
});
let calendarEvents = [];
users.forEach((u) => {
let arr = results
.filter((i) => i.user.id === u.id)
.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
calendarRangeId: i.id,
sourceColor: u.color,
//display: 'background' // can be an option for the disponibility
}));
calendarEvents.push({
events: arr,
color: u.color,
textColor: "#444444",
editable: false,
id: u.id,
});
});
this.users.loaded = users;
this.options = users;
this.calendarEvents.loaded = calendarEvents;
whoami().then(
(me) =>
new Promise((resolve, reject) => {
this.users.logged = me;
let currentUser = users.find(
(u) => u.id === me.id,
);
this.value = currentUser;
fetchCalendar(currentUser.id).then(
(calendar) =>
new Promise(
(resolve, reject) => {
let results =
calendar.results;
let events =
results.map(
(i) => ({
start: i
.startDate
.datetime,
end: i
.endDate
.datetime,
}),
);
let calendarEventsCurrentUser =
{
events: events,
color: "darkblue",
id: 1000,
editable: false,
};
this.calendarEvents.user =
calendarEventsCurrentUser;
this.selectUsers(
currentUser,
);
resolve();
},
),
);
resolve();
}),
);
resolve();
}),
);
)
.catch((error) => {
this.errorMsg.push(error.message);
});
},
transName(value) {
return `${value.username}`;
},
coloriseSelectedValues() {
let tags = document.querySelectorAll(
"div.multiselect__tags-wrap",
)[0];
resolve();
}),
);
resolve();
}),
)
.catch((error) => {
this.errorMsg.push(error.message);
});
},
transName(value) {
return `${value.username}`;
},
coloriseSelectedValues() {
let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0];
if (tags.hasChildNodes()) {
let children = tags.childNodes;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.nodeType === Node.ELEMENT_NODE) {
this.users.selected.forEach((u) => {
if (child.hasChildNodes()) {
if (child.firstChild.innerText == u.username) {
child.style.background = u.color;
child.firstChild.style.color = "#444444";
if (tags.hasChildNodes()) {
let children = tags.childNodes;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.nodeType === Node.ELEMENT_NODE) {
this.users.selected.forEach((u) => {
if (child.hasChildNodes()) {
if (child.firstChild.innerText == u.username) {
child.style.background = u.color;
child.firstChild.style.color = "#444444";
}
}
});
}
}
}
});
}
}
}
}
},
selectEvents() {
let selectedUsersId = this.users.selected.map((a) => a.id);
this.calendarEvents.selected = this.calendarEvents.loaded.filter(
(a) => selectedUsersId.includes(a.id),
);
},
selectUsers(value) {
this.users.selected.push(value);
this.coloriseSelectedValues();
this.selectEvents();
this.updateEventsSource();
},
unSelectUsers(value) {
this.users.selected = this.users.selected.filter(
(a) => a.id != value.id,
);
this.selectEvents();
this.updateEventsSource();
},
},
selectEvents() {
let selectedUsersId = this.users.selected.map((a) => a.id);
this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) =>
selectedUsersId.includes(a.id),
);
mounted() {
this.init();
},
selectUsers(value) {
this.users.selected.push(value);
this.coloriseSelectedValues();
this.selectEvents();
this.updateEventsSource();
},
unSelectUsers(value) {
this.users.selected = this.users.selected.filter((a) => a.id != value.id);
this.selectEvents();
this.updateEventsSource();
},
},
mounted() {
this.init();
},
};
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,54 +1,59 @@
<template>
<div>
<template v-if="templates.length > 0">
<slot name="title">
<h2>{{ $t("generate_document") }}</h2>
</slot>
<div class="container">
<div class="row">
<div class="col-md-4">
<slot name="label">
<label>{{ $t("select_a_template") }}</label>
<div>
<template v-if="templates.length > 0">
<slot name="title">
<h2>{{ $t("generate_document") }}</h2>
</slot>
</div>
<div class="col-md-8">
<div class="input-group mb-3">
<select class="form-select" v-model="template">
<option disabled selected value="">
{{ $t("choose_a_template") }}
</option>
<template v-for="t in templates" :key="t.id">
<option :value="t.id">
{{ localizeString(t.name) || "Aucun nom défini" }}
</option>
</template>
</select>
<a
v-if="canGenerate"
class="btn btn-update btn-sm change-icon"
:href="buildUrlGenerate"
@click.prevent="clickGenerate($event, buildUrlGenerate)"
><i class="fa fa-fw fa-cog"
/></a>
<a
v-else
class="btn btn-update btn-sm change-icon"
href="#"
disabled
><i class="fa fa-fw fa-cog"
/></a>
<div class="container">
<div class="row">
<div class="col-md-4">
<slot name="label">
<label>{{ $t("select_a_template") }}</label>
</slot>
</div>
<div class="col-md-8">
<div class="input-group mb-3">
<select class="form-select" v-model="template">
<option disabled selected value="">
{{ $t("choose_a_template") }}
</option>
<template v-for="t in templates" :key="t.id">
<option :value="t.id">
{{
localizeString(t.name) ||
"Aucun nom défini"
}}
</option>
</template>
</select>
<a
v-if="canGenerate"
class="btn btn-update btn-sm change-icon"
:href="buildUrlGenerate"
@click.prevent="
clickGenerate($event, buildUrlGenerate)
"
><i class="fa fa-fw fa-cog"
/></a>
<a
v-else
class="btn btn-update btn-sm change-icon"
href="#"
disabled
><i class="fa fa-fw fa-cog"
/></a>
</div>
</div>
</div>
<div class="row" v-if="hasDescription">
<div class="col-md-8 align-self-end">
<p>{{ getDescription }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="row" v-if="hasDescription">
<div class="col-md-8 align-self-end">
<p>{{ getDescription }}</p>
</div>
</div>
</div>
</template>
</div>
</template>
</div>
</template>
<script>
@@ -56,83 +61,83 @@ import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "PickTemplate",
props: {
entityId: [String, Number],
entityClass: {
type: String,
required: false,
name: "PickTemplate",
props: {
entityId: [String, Number],
entityClass: {
type: String,
required: false,
},
templates: {
type: Array,
required: true,
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
},
templates: {
type: Array,
required: true,
emits: ["goToGenerateDocument"],
data() {
return {
template: null,
};
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
},
emits: ["goToGenerateDocument"],
data() {
return {
template: null,
};
},
computed: {
canGenerate() {
return this.template != null;
},
hasDescription() {
if (this.template == null) {
return false;
}
computed: {
canGenerate() {
return this.template != null;
},
hasDescription() {
if (this.template == null) {
return false;
}
return true;
},
getDescription() {
if (null === this.template) {
return "";
}
let desc = this.templates.find((t) => t.id === this.template);
if (null === desc) {
return "";
}
return desc.description || "";
},
buildUrlGenerate() {
if (null === this.template) {
return "#";
}
return true;
},
getDescription() {
if (null === this.template) {
return "";
}
let desc = this.templates.find((t) => t.id === this.template);
if (null === desc) {
return "";
}
return desc.description || "";
},
buildUrlGenerate() {
if (null === this.template) {
return "#";
}
return buildLink(this.template, this.entityId, this.entityClass);
return buildLink(this.template, this.entityId, this.entityClass);
},
},
},
methods: {
localizeString(str) {
return localizeString(str);
},
clickGenerate(event, link) {
if (!this.preventDefaultMoveToGenerate) {
window.location.assign(link);
}
methods: {
localizeString(str) {
return localizeString(str);
},
clickGenerate(event, link) {
if (!this.preventDefaultMoveToGenerate) {
window.location.assign(link);
}
this.$emit("goToGenerateDocument", {
event,
link,
template: this.template,
});
this.$emit("goToGenerateDocument", {
event,
link,
template: this.template,
});
},
},
},
i18n: {
messages: {
fr: {
generate_document: "Générer un document",
select_a_template: "Choisir un modèle",
choose_a_template: "Choisir",
},
i18n: {
messages: {
fr: {
generate_document: "Générer un document",
select_a_template: "Choisir un modèle",
choose_a_template: "Choisir",
},
},
},
},
};
</script>

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
docgen:
# Geen ICU berichten nodig voor data_dump_email meer

View File

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

View File

@@ -94,7 +94,7 @@ class StoredObject implements Document, TrackCreationInterface
/**
* @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion>
*/
#[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true, fetch: 'EAGER')]
#[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true)]
private Collection&Selectable $versions;
/**

View File

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

View File

@@ -6,20 +6,20 @@ const algo = "AES-CBC";
const URL_POST = "/asyncupload/temp_url/generate/post";
const keyDefinition = {
name: algo,
length: 256,
name: algo,
length: 256,
};
const createFilename = (): string => {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 7; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
for (let i = 0; i < 7; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
return text;
};
/**
@@ -30,59 +30,59 @@ const createFilename = (): string => {
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
*/
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
};
export const uploadVersion = async (
uploadFile: ArrayBuffer,
storedObject: StoredObject,
uploadFile: ArrayBuffer,
storedObject: StoredObject,
): Promise<string> => {
const params = new URLSearchParams();
params.append("expires_delay", "180");
params.append("submit_delay", "180");
const asyncData: PostStoreObjectSignature = await makeFetch(
"GET",
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
"?" +
params.toString(),
);
const suffix = createFilename();
const filename = asyncData.prefix + suffix;
const formData = new FormData();
formData.append("redirect", asyncData.redirect);
formData.append("max_file_size", asyncData.max_file_size.toString());
formData.append("max_file_count", asyncData.max_file_count.toString());
formData.append("expires", asyncData.expires.toString());
formData.append("signature", asyncData.signature);
formData.append(filename, new Blob([uploadFile]), suffix);
const params = new URLSearchParams();
params.append("expires_delay", "180");
params.append("submit_delay", "180");
const asyncData: PostStoreObjectSignature = await makeFetch(
"GET",
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
"?" +
params.toString(),
);
const suffix = createFilename();
const filename = asyncData.prefix + suffix;
const formData = new FormData();
formData.append("redirect", asyncData.redirect);
formData.append("max_file_size", asyncData.max_file_size.toString());
formData.append("max_file_count", asyncData.max_file_count.toString());
formData.append("expires", asyncData.expires.toString());
formData.append("signature", asyncData.signature);
formData.append(filename, new Blob([uploadFile]), suffix);
const response = await window.fetch(asyncData.url, {
method: "POST",
body: formData,
});
const response = await window.fetch(asyncData.url, {
method: "POST",
body: formData,
});
if (!response.ok) {
console.error("Error while sending file to store", response);
throw new Error(response.statusText);
}
if (!response.ok) {
console.error("Error while sending file to store", response);
throw new Error(response.statusText);
}
return Promise.resolve(filename);
return Promise.resolve(filename);
};
export const encryptFile = async (
originalFile: ArrayBuffer,
originalFile: ArrayBuffer,
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
const iv = crypto.getRandomValues(new Uint8Array(16));
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
"encrypt",
"decrypt",
]);
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
const encrypted = await window.crypto.subtle.encrypt(
{ name: algo, iv: iv },
key,
originalFile,
);
const iv = crypto.getRandomValues(new Uint8Array(16));
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
"encrypt",
"decrypt",
]);
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
const encrypted = await window.crypto.subtle.encrypt(
{ name: algo, iv: iv },
key,
originalFile,
);
return Promise.resolve([encrypted, iv, exportedKey]);
return Promise.resolve([encrypted, iv, exportedKey]);
};

View File

@@ -2,9 +2,9 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
export function fetch_generic_docs_by_accompanying_period(
periodId: number,
periodId: number,
): Promise<GenericDocForAccompanyingPeriod[]> {
return fetchResults(
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
);
return fetchResults(
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
);
}

View File

@@ -6,116 +6,117 @@ import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vue
const i18n = _createI18n({});
const startApp = (
divElement: HTMLDivElement,
collectionEntry: null | HTMLLIElement,
divElement: HTMLDivElement,
collectionEntry: null | HTMLLIElement,
): void => {
console.log("app started", divElement);
console.log("app started", divElement);
const inputTitle = collectionEntry?.querySelector("input[type='text']");
const inputTitle = collectionEntry?.querySelector("input[type='text']");
const input_stored_object: HTMLInputElement | null = divElement.querySelector(
"input[data-stored-object]",
);
if (null === input_stored_object) {
throw new Error("input to stored object not found");
}
const input_stored_object: HTMLInputElement | null =
divElement.querySelector("input[data-stored-object]");
if (null === input_stored_object) {
throw new Error("input to stored object not found");
}
let existingDoc: StoredObject | null = null;
if (input_stored_object.value !== "") {
existingDoc = JSON.parse(input_stored_object.value);
}
const app_container = document.createElement("div");
divElement.appendChild(app_container);
let existingDoc: StoredObject | null = null;
if (input_stored_object.value !== "") {
existingDoc = JSON.parse(input_stored_object.value);
}
const app_container = document.createElement("div");
divElement.appendChild(app_container);
const app = createApp({
template:
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
data() {
return {
existingDoc: existingDoc,
inputTitle: inputTitle,
};
},
components: {
DropFileWidget,
},
methods: {
addDocument: function ({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void {
stored_object.title = file_name;
console.log("object added", stored_object);
console.log("version added", stored_object_version);
this.$data.existingDoc = stored_object;
this.$data.existingDoc.currentVersion = stored_object_version;
input_stored_object.value = JSON.stringify(this.$data.existingDoc);
if (this.$data.inputTitle) {
if (!this.$data.inputTitle?.value) {
this.$data.inputTitle.value = file_name;
}
}
},
removeDocument: function (object: StoredObject): void {
console.log("catch remove document", object);
input_stored_object.value = "";
this.$data.existingDoc = undefined;
console.log("collectionEntry", collectionEntry);
const app = createApp({
template:
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
data() {
return {
existingDoc: existingDoc,
inputTitle: inputTitle,
};
},
components: {
DropFileWidget,
},
methods: {
addDocument: function ({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void {
stored_object.title = file_name;
console.log("object added", stored_object);
console.log("version added", stored_object_version);
this.$data.existingDoc = stored_object;
this.$data.existingDoc.currentVersion = stored_object_version;
input_stored_object.value = JSON.stringify(
this.$data.existingDoc,
);
if (this.$data.inputTitle) {
if (!this.$data.inputTitle?.value) {
this.$data.inputTitle.value = file_name;
}
}
},
removeDocument: function (object: StoredObject): void {
console.log("catch remove document", object);
input_stored_object.value = "";
this.$data.existingDoc = undefined;
console.log("collectionEntry", collectionEntry);
if (null !== collectionEntry) {
console.log("will remove collection");
collectionEntry.remove();
}
},
},
});
if (null !== collectionEntry) {
console.log("will remove collection");
collectionEntry.remove();
}
},
},
});
app.use(i18n).mount(app_container);
app.use(i18n).mount(app_container);
};
window.addEventListener("collection-add-entry", ((
e: CustomEvent<CollectionEventPayload>,
e: CustomEvent<CollectionEventPayload>,
) => {
const detail = e.detail;
const divElement: null | HTMLDivElement = detail.entry.querySelector(
"div[data-stored-object]",
);
const detail = e.detail;
const divElement: null | HTMLDivElement = detail.entry.querySelector(
"div[data-stored-object]",
);
if (null === divElement) {
throw new Error("div[data-stored-object] not found");
}
if (null === divElement) {
throw new Error("div[data-stored-object] not found");
}
startApp(divElement, detail.entry);
startApp(divElement, detail.entry);
}) as EventListener);
window.addEventListener("DOMContentLoaded", () => {
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
"div[data-stored-object]",
);
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
"div[data-stored-object]",
);
upload_inputs.forEach((input: HTMLDivElement): void => {
// test for a parent to check if this is a collection entry
let collectionEntry: null | HTMLLIElement = null;
const parent = input.parentElement;
console.log("parent", parent);
if (null !== parent) {
const grandParent = parent.parentElement;
console.log("grandParent", grandParent);
if (null !== grandParent) {
if (
grandParent.tagName.toLowerCase() === "li" &&
grandParent.classList.contains("entry")
) {
collectionEntry = grandParent as HTMLLIElement;
upload_inputs.forEach((input: HTMLDivElement): void => {
// test for a parent to check if this is a collection entry
let collectionEntry: null | HTMLLIElement = null;
const parent = input.parentElement;
console.log("parent", parent);
if (null !== parent) {
const grandParent = parent.parentElement;
console.log("grandParent", grandParent);
if (null !== grandParent) {
if (
grandParent.tagName.toLowerCase() === "li" &&
grandParent.classList.contains("entry")
) {
collectionEntry = grandParent as HTMLLIElement;
}
}
}
}
}
startApp(input, collectionEntry);
});
startApp(input, collectionEntry);
});
});
export {};

View File

@@ -9,26 +9,26 @@ import ToastPlugin from "vue-toast-notification";
const i18n = _createI18n({});
window.addEventListener("DOMContentLoaded", function (e) {
document
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
.forEach((el) => {
const storedObject = JSON.parse(
el.dataset.storedObject as string,
) as StoredObject;
const title = el.dataset.title as string;
const app = createApp({
components: { DownloadButton },
data() {
return {
storedObject,
title,
classes: { btn: true, "btn-outline-primary": true },
};
},
template:
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
});
document
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
.forEach((el) => {
const storedObject = JSON.parse(
el.dataset.storedObject as string,
) as StoredObject;
const title = el.dataset.title as string;
const app = createApp({
components: { DownloadButton },
data() {
return {
storedObject,
title,
classes: { btn: true, "btn-outline-primary": true },
};
},
template:
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
});
app.use(i18n).use(ToastPlugin).mount(el);
});
app.use(i18n).use(ToastPlugin).mount(el);
});
});

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