Compare commits

..

39 Commits

Author SHA1 Message Date
09d81d8025 Initiate the ZimbraBundle 2025-11-24 09:31:07 +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
526 changed files with 21136 additions and 40927 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,6 @@
kind: Fixed
body: Insert name of file as the document title when uploading
time: 2025-11-19T13:33:24.778116633+01:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: Add missing path paramater 'id' for editing multiple participations
time: 2025-11-19T13:48:02.078949572+01:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -0,0 +1,7 @@
kind: Fixed
body: |
Hide the display of inactive user groups in the api
time: 2025-11-19T16:12:33.803084517+01:00
custom:
Issue: "471"
SchemaChange: No schema change

View File

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

View File

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

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.

View File

@@ -23,7 +23,7 @@ max_line_length = 0
indent_size = 2 indent_size = 2
indent_style = space indent_style = space
[*.rst] [.rst]
indent_size = 3 ident_size = 3
indent_style = space ident_style = space

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 #### 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 ```bash
# Run all tests
vendor/bin/phpunit
# Run a specific test file # Run a specific test file
vendor/bin/phpunit path/to/TestFile.php 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,38 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v4.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 ## v4.6.1 - 2025-10-27
### Fixed ### Fixed
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php * Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php
@@ -780,7 +812,7 @@ Fix color of Chill footer
- ajout d'un filtre et regroupement par usager participant sur les échanges - ajout d'un filtre et regroupement par usager participant sur les échanges
- ajout d'un regroupement: par type d'activité associé au parcours; - ajout d'un regroupement: par type d'activité associé au parcours;
- trie les filtre et regroupements par ordre alphabétique dans els exports - trie les filtre et regroupements par ordre alphabétique dans els exports
- ajout d'un paramètre qui permet de désactiver le filtre par territoire dans les exports - ajout d'un paramètre qui permet de désactiver le filtre par centre dans les exports
- correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date" - correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date"
## v2.9.2 - 2023-10-17 ## v2.9.2 - 2023-10-17
@@ -960,7 +992,7 @@ error when trying to reedit a saved export
- ajout d'un regroupement par métier des intervenants sur un parcours; - ajout d'un regroupement par métier des intervenants sur un parcours;
- ajout d'un regroupement par service des intervenants sur un parcours; - ajout d'un regroupement par service des intervenants sur un parcours;
- ajout d'un regroupement par utilisateur intervenant sur un parcours - ajout d'un regroupement par utilisateur intervenant sur un parcours
- ajout d'un regroupement "par territoire de l'usager"; - ajout d'un regroupement "par centre de l'usager";
- ajout d'un filtre "par métier intervenant sur un parcours"; - ajout d'un filtre "par métier intervenant sur un parcours";
- ajout d'un filtre "par service intervenant sur un parcours"; - ajout d'un filtre "par service intervenant sur un parcours";
- création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot); - création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot);

View File

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

View File

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

View File

@@ -7,6 +7,13 @@
"chill", "chill",
"social worker" "social worker"
], ],
"repositories": [{
"type": "path",
"url": "./packages/ChillZimbraBundle",
"options": {
"symlink": true
}
}],
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"ext-dom": "*", "ext-dom": "*",
@@ -14,6 +21,7 @@
"ext-openssl": "*", "ext-openssl": "*",
"ext-redis": "*", "ext-redis": "*",
"ext-zlib": "*", "ext-zlib": "*",
"chill-project/chill-zimbra-bundle": "@dev",
"champs-libres/wopi-bundle": "dev-symfony-v5@dev", "champs-libres/wopi-bundle": "dev-symfony-v5@dev",
"champs-libres/wopi-lib": "dev-master@dev", "champs-libres/wopi-lib": "dev-master@dev",
"doctrine/data-fixtures": "^1.8", "doctrine/data-fixtures": "^1.8",
@@ -133,7 +141,6 @@
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle", "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle", "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src", "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
"Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
"Chill\\Utils\\Rector\\": "utils/rector/src" "Chill\\Utils\\Rector\\": "utils/rector/src"
} }
}, },

View File

@@ -34,8 +34,8 @@ return [
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true], Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true], Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
Chill\WopiBundle\ChillWopiBundle::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\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true], Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true], loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
Chill\ZimbraBundle\ChillZimbraBundle::class => ['all' => true],
]; ];

View File

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

View File

@@ -1,5 +1,5 @@
chill_doc_store: chill_doc_store:
use_driver: local_storage use_driver: openstack
local_storage: local_storage:
storage_path: '%kernel.project_dir%/var/storage' storage_path: '%kernel.project_dir%/var/storage'
openstack: 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\Calendar': '@ChillCalendarBundle/migrations'
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations' 'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
'Chill\Migrations\Report': '@ChillReportBundle/migrations' 'Chill\Migrations\Report': '@ChillReportBundle/migrations'
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
all_or_nothing: all_or_nothing:
true true

View File

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

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 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:: .. warning::
This part of the doc is not yet tested This part of the doc is not yet tested
Create a new directory with Bundle class TODO
----------------------------------------
.. 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",
}
}
}
Register the bundle .. rubric:: Footnotes
-------------------
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
.. [#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

@@ -11,6 +11,7 @@
"@hotwired/stimulus": "^3.0.0", "@hotwired/stimulus": "^3.0.0",
"@luminateone/eslint-baseline": "^1.0.9", "@luminateone/eslint-baseline": "^1.0.9",
"@symfony/stimulus-bridge": "^3.2.0", "@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
"@symfony/webpack-encore": "^4.1.0", "@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
@@ -41,7 +42,6 @@
"typescript": "^5.6.3", "typescript": "^5.6.3",
"typescript-eslint": "^8.13.0", "typescript-eslint": "^8.13.0",
"vue-loader": "^17.0.0", "vue-loader": "^17.0.0",
"vue-tsc": "^3.1.3",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.1" "webpack-cli": "^5.0.1"
}, },
@@ -81,12 +81,12 @@
"dev": "encore dev", "dev": "encore dev",
"watch": "encore dev --watch", "watch": "encore dev --watch",
"build": "encore production --progress", "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-validate": "swagger-cli validate templates/api/specs.yaml",
"specs-create-dir": "mkdir -p templates/api", "specs-create-dir": "mkdir -p templates/api",
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate", "specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
"version": "node --version", "version": "node --version",
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\"" "eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
}, },
"private": true "private": true
} }

View File

@@ -0,0 +1,22 @@
{
"name": "chill-project/chill-zimbra-bundle",
"description": "Provide connection between Zimbra agenda and Chill",
"minimum-stability": "stable",
"license": "AGPL-3.0",
"type": "library",
"keywords": [
"chill",
"social worker"
],
"require": {
"chill-project/chill-bundles": "dev-master@dev",
"zimbra-api/soap-api": "^3.2.2",
"psr/http-client": "^1.0",
"nyholm/psr7": "^1.0"
},
"autoload": {
"psr-4": {
"Chill\\ZimbraBundle\\": "src/"
}
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Chill\ZimbraBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillZimbraBundle extends Bundle
{
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Chill\ZimbraBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class ChillZimbraExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml');
}
}

View File

@@ -0,0 +1,8 @@
services:
_defaults:
autoconfigure: true
autowire: true
Chill\ZimbraBundle\Command\:
resource: '../Command'
tags: ['console.command']

View File

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

@@ -88,8 +88,8 @@ class ActivityType extends AbstractType
if (null !== $options['data']->getPerson()) { if (null !== $options['data']->getPerson()) {
$builder->add('scope', ScopePickerType::class, [ $builder->add('scope', ScopePickerType::class, [
'center' => $options['center'],
'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'], 'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'],
'center' => $options['center'],
'required' => true, 'required' => true,
]); ]);
} }

View File

@@ -10,7 +10,10 @@
/> />
</div> </div>
<div <div
v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0" v-if="
getContext === 'accompanyingCourse' &&
suggestedEntities.length > 0
"
> >
<ul class="list-suggest add-items inline"> <ul class="list-suggest add-items inline">
<li <li

View File

@@ -39,11 +39,17 @@
<option selected disabled value=""> <option selected disabled value="">
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }} {{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
</option> </option>
<option v-for="t in locationTypes" :value="t" :key="t.id"> <option
v-for="t in locationTypes"
:value="t"
:key="t.id"
>
{{ localizeString(t.title) }} {{ localizeString(t.title) }}
</option> </option>
</select> </select>
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label> <label>{{
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
}}</label>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
@@ -102,7 +108,10 @@
</form> </form>
</template> </template>
<template #footer> <template #footer>
<button class="btn btn-save" @click.prevent="saveNewLocation"> <button
class="btn btn-save"
@click.prevent="saveNewLocation"
>
{{ trans(SAVE) }} {{ trans(SAVE) }}
</button> </button>
</template> </template>
@@ -235,7 +244,8 @@ export default {
}, },
hasPhonenumber1() { hasPhonenumber1() {
return ( return (
this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== "" this.selected.phonenumber1 !== null &&
this.selected.phonenumber1 !== ""
); );
}, },
showAddAddress() { showAddAddress() {

View File

@@ -43,11 +43,23 @@ export default {
span.badge { span.badge {
@include badge_social($social-action-color); @include badge_social($social-action-color);
font-size: 95%; font-size: 95%;
white-space: normal;
word-wrap: break-word;
word-break: break-word;
display: inline-block;
max-width: 100%;
margin-bottom: 5px; margin-bottom: 5px;
margin-right: 1em; margin-right: 1em;
max-width: 100%; /* Adjust as needed */ text-align: left;
overflow: hidden; line-height: 1.2em;
text-overflow: ellipsis;
white-space: nowrap; &::before {
position: absolute;
left: 11px;
top: 0;
margin: 0 0.3em 0 -0.75em;
}
position: relative;
padding-left: 1.5em;
} }
</style> </style>

View File

@@ -10,7 +10,9 @@
:value="issue" :value="issue"
/> />
<label class="form-check-label" :for="issue.id"> <label class="form-check-label" :for="issue.id">
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span> <span class="badge bg-chill-l-gray text-dark">{{
issue.text
}}</span>
</label> </label>
</div> </div>
</span> </span>
@@ -41,7 +43,22 @@ export default {
span.badge { span.badge {
@include badge_social($social-issue-color); @include badge_social($social-issue-color);
font-size: 95%; font-size: 95%;
white-space: normal;
word-wrap: break-word;
word-break: break-word;
display: inline-block;
max-width: 100%;
margin-bottom: 5px; margin-bottom: 5px;
margin-right: 1em; 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> </style>

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Controller; namespace Chill\CalendarBundle\Controller;
use Chill\CalendarBundle\Repository\CalendarRepository; use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\CalendarBundle\Repository\InviteRepository;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
class CalendarAPIController extends ApiController 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'])] #[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 public function listByUser(User $user, Request $request, string $_format): JsonResponse
@@ -52,16 +56,37 @@ class CalendarAPIController extends ApiController
throw new BadRequestHttpException('dateTo not parsable'); throw new BadRequestHttpException('dateTo not parsable');
} }
$total = $this->calendarRepository->countByUser($user, $dateFrom, $dateTo); // Get calendar items where user is the main user
$paginator = $this->getPaginatorFactory()->create($total); $ownCalendars = $this->calendarRepository->findByUser(
$ranges = $this->calendarRepository->findByUser(
$user, $user,
$dateFrom, $dateFrom,
$dateTo, $dateTo
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
); );
// 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); $collection = new Collection($ranges, $paginator);
return $this->json($collection, Response::HTTP_OK, [], ['groups' => ['calendar:light']]); return $this->json($collection, Response::HTTP_OK, [], ['groups' => ['calendar:light']]);

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Repository; namespace Chill\CalendarBundle\Repository;
use Chill\CalendarBundle\Entity\Invite; use Chill\CalendarBundle\Entity\Invite;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository; use Doctrine\Persistence\ObjectRepository;
@@ -51,6 +52,52 @@ class InviteRepository implements ObjectRepository
return $this->entityRepository->findOneBy($criteria); 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 public function getClassName(): string
{ {
return Invite::class; return Invite::class;

View File

@@ -68,7 +68,9 @@ export type EventInputCalendarRange = EventInput & {
export function isEventInputCalendarRange( export function isEventInputCalendarRange(
toBeDetermined: EventInputCalendarRange | EventInput, toBeDetermined: EventInputCalendarRange | EventInput,
): toBeDetermined is EventInputCalendarRange { ): toBeDetermined is EventInputCalendarRange {
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"; return (
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
);
} }
export {}; export {};

View File

@@ -4,9 +4,18 @@
{{ user.text }} {{ user.text }}
<template v-if="invite !== null"> <template v-if="invite !== null">
<i v-if="invite.status === 'accepted'" class="fa fa-check" /> <i v-if="invite.status === 'accepted'" class="fa fa-check" />
<i v-else-if="invite.status === 'declined'" class="fa fa-times" /> <i
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" /> v-else-if="invite.status === 'declined'"
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" /> 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> <span v-else="">{{ invite.status }}</span>
</template> </template>
</span> </span>
@@ -60,7 +69,8 @@ export default {
computed: { computed: {
style() { style() {
return { return {
backgroundColor: this.$store.getters.getUserData(this.user).mainColor, backgroundColor: this.$store.getters.getUserData(this.user)
.mainColor,
}; };
}, },
rangeShow: { rangeShow: {
@@ -71,7 +81,9 @@ export default {
}); });
}, },
get() { get() {
return this.$store.getters.isRangeShownOnCalendarForUser(this.user); return this.$store.getters.isRangeShownOnCalendarForUser(
this.user,
);
}, },
}, },
remoteShow: { remoteShow: {
@@ -82,7 +94,9 @@ export default {
}); });
}, },
get() { get() {
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user); return this.$store.getters.isRemoteShownOnCalendarForUser(
this.user,
);
}, },
}, },
}, },

View File

@@ -22,25 +22,33 @@
</button> </button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1"> <ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<li v-if="status !== Statuses.ACCEPTED"> <li v-if="status !== Statuses.ACCEPTED">
<a class="dropdown-item" @click="changeStatus(Statuses.ACCEPTED)" <a
><i class="fa fa-check" aria-hidden="true"></i> {{ $t("Accept") }}</a class="dropdown-item"
@click="changeStatus(Statuses.ACCEPTED)"
><i class="fa fa-check" aria-hidden="true"></i>
{{ $t("Accept") }}</a
> >
</li> </li>
<li v-if="status !== Statuses.DECLINED"> <li v-if="status !== Statuses.DECLINED">
<a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)" <a
><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a class="dropdown-item"
@click="changeStatus(Statuses.DECLINED)"
><i class="fa fa-times" aria-hidden="true"></i>
{{ $t("Decline") }}</a
> >
</li> </li>
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED"> <li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
<a <a
class="dropdown-item" class="dropdown-item"
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)" @click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a ><i class="fa fa-question"></i>
{{ $t("Tentatively_accept") }}</a
> >
</li> </li>
<li v-if="status !== Statuses.PENDING"> <li v-if="status !== Statuses.PENDING">
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)" <a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a ><i class="fa fa-hourglass-o"></i>
{{ $t("Set_pending") }}</a
> >
</li> </li>
</ul> </ul>
@@ -83,7 +91,9 @@ export default defineComponent({
}, },
}, },
emits: { emits: {
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") { statusChanged(
payload: "accepted" | "declined" | "pending" | "tentative",
) {
return true; return true;
}, },
}, },

View File

@@ -108,9 +108,12 @@
{{ formatDate(event.endStr, "time") }}: {{ formatDate(event.endStr, "time") }}:
{{ event.extendedProps.locationName }}</b {{ event.extendedProps.locationName }}</b
> >
<b v-else-if="event.extendedProps.is === 'local'">{{ <a
event.title :href="calendarLink(event.id)"
}}</b> v-else-if="event.extendedProps.is === 'local'"
>
<b>{{ event.title }}</b>
</a>
<b v-else>no 'is'</b> <b v-else>no 'is'</b>
<a <a
v-if="event.extendedProps.is === 'range'" 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(() => { onMounted(() => {
copyFromWeek.value = dateToISO(getMonday(0)); copyFromWeek.value = dateToISO(getMonday(0));
copyToWeek.value = dateToISO(getMonday(1)); copyToWeek.value = dateToISO(getMonday(1));

View File

@@ -41,7 +41,9 @@ const futureStore = function (): Promise<Store<State>> {
}); });
store.commit("me/setWhoAmi", user, { root: true }); store.commit("me/setWhoAmi", user, { root: true });
store.dispatch("locations/getLocations", null, { root: true }).then((_) => { store
.dispatch("locations/getLocations", null, { root: true })
.then((_) => {
return store.dispatch("locations/getCurrentLocation", null, { return store.dispatch("locations/getCurrentLocation", null, {
root: true, root: true,
}); });

View File

@@ -29,7 +29,10 @@ export default {
(state: CalendarLocalsState) => (state: CalendarLocalsState) =>
({ start, end }: { start: Date; end: Date }): boolean => { ({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.localsLoaded) { for (const range of state.localsLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) { if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true; return true;
} }
} }
@@ -51,7 +54,10 @@ export default {
}); });
state.key = state.key + toAdd.length; state.key = state.key + toAdd.length;
}, },
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) { addLoaded(
state: CalendarLocalsState,
payload: { start: Date; end: Date },
) {
state.localsLoaded.push({ state.localsLoaded.push({
start: payload.start.getTime(), start: payload.start.getTime(),
end: payload.end.getTime(), end: payload.end.getTime(),
@@ -79,7 +85,11 @@ export default {
end: end, end: end,
}); });
return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end) return fetchCalendarLocalForUser(
ctx.rootGetters["me/getMe"],
start,
end,
)
.then((remotes: CalendarLight[]) => { .then((remotes: CalendarLight[]) => {
// to be add when reactivity problem will be solve ? // to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes); //ctx.commit('addRemotes', remotes);

View File

@@ -40,7 +40,10 @@ export default {
(state: CalendarRangesState) => (state: CalendarRangesState) =>
({ start, end }: { start: Date; end: Date }): boolean => { ({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.rangesLoaded) { for (const range of state.rangesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) { if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true; return true;
} }
} }
@@ -107,7 +110,9 @@ export default {
state: CalendarRangesState, state: CalendarRangesState,
externalEvents: (EventInput & { id: string })[], externalEvents: (EventInput & { id: string })[],
) { ) {
const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id)); const toAdd = externalEvents.filter(
(r) => !state.rangesIndex.has(r.id),
);
toAdd.forEach((r) => { toAdd.forEach((r) => {
state.rangesIndex.add(r.id); state.rangesIndex.add(r.id);
@@ -115,7 +120,10 @@ export default {
}); });
state.key = state.key + toAdd.length; state.key = state.key + toAdd.length;
}, },
addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) { addLoaded(
state: CalendarRangesState,
payload: { start: Date; end: Date },
) {
state.rangesLoaded.push({ state.rangesLoaded.push({
start: payload.start.getTime(), start: payload.start.getTime(),
end: payload.end.getTime(), end: payload.end.getTime(),
@@ -134,12 +142,17 @@ export default {
}, },
removeRange(state: CalendarRangesState, calendarRangeId: number) { removeRange(state: CalendarRangesState, calendarRangeId: number) {
const found = state.ranges.find( const found = state.ranges.find(
(r) => r.calendarRangeId === calendarRangeId && r.is === "range", (r) =>
r.calendarRangeId === calendarRangeId && r.is === "range",
); );
if (found !== undefined) { if (found !== undefined) {
state.ranges = state.ranges.filter( state.ranges = state.ranges.filter(
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"), (r) =>
!(
r.calendarRangeId === calendarRangeId &&
r.is === "range"
),
); );
if (typeof found.id === "string") { if (typeof found.id === "string") {
@@ -198,7 +211,11 @@ export default {
}, },
createRange( createRange(
ctx: Context, ctx: Context,
{ start, end, location }: { start: Date; end: Date; location: Location }, {
start,
end,
location,
}: { start: Date; end: Date; location: Location },
): Promise<null> { ): Promise<null> {
const url = `/api/1.0/calendar/calendar-range.json?`; const url = `/api/1.0/calendar/calendar-range.json?`;
@@ -223,7 +240,11 @@ export default {
}, },
} as CalendarRangeCreate; } as CalendarRangeCreate;
return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body) return makeFetch<CalendarRangeCreate, CalendarRange>(
"POST",
url,
body,
)
.then((newRange) => { .then((newRange) => {
ctx.commit("addRange", newRange); ctx.commit("addRange", newRange);
@@ -260,7 +281,11 @@ export default {
}, },
} as CalendarRangeEdit; } as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body) return makeFetch<CalendarRangeEdit, CalendarRange>(
"PATCH",
url,
body,
)
.then((range) => { .then((range) => {
ctx.commit("updateRange", range); ctx.commit("updateRange", range);
return Promise.resolve(null); return Promise.resolve(null);
@@ -285,7 +310,11 @@ export default {
}, },
} as CalendarRangeEdit; } as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body) return makeFetch<CalendarRangeEdit, CalendarRange>(
"PATCH",
url,
body,
)
.then((range) => { .then((range) => {
ctx.commit("updateRange", range); ctx.commit("updateRange", range);
return Promise.resolve(null); return Promise.resolve(null);
@@ -305,14 +334,20 @@ export default {
for (const r of rangesToCopy) { for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date); const start = new Date(ISOToDatetime(r.start) as Date);
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate()); start.setFullYear(
to.getFullYear(),
to.getMonth(),
to.getDate(),
);
const end = new Date(ISOToDatetime(r.end) as Date); const end = new Date(ISOToDatetime(r.end) as Date);
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate()); end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const location = ctx.rootGetters["locations/getLocationById"]( const location = ctx.rootGetters["locations/getLocationById"](
r.locationId, r.locationId,
); );
promises.push(ctx.dispatch("createRange", { start, end, location })); promises.push(
ctx.dispatch("createRange", { start, end, location }),
);
} }
return Promise.all(promises).then(() => Promise.resolve(null)); return Promise.all(promises).then(() => Promise.resolve(null));
@@ -334,7 +369,9 @@ export default {
r.locationId, r.locationId,
); );
promises.push(ctx.dispatch("createRange", { start, end, location })); promises.push(
ctx.dispatch("createRange", { start, end, location }),
);
} }
return Promise.all(promises).then(() => Promise.resolve(null)); return Promise.all(promises).then(() => Promise.resolve(null));

View File

@@ -29,7 +29,10 @@ export default {
(state: CalendarRemotesState) => (state: CalendarRemotesState) =>
({ start, end }: { start: Date; end: Date }): boolean => { ({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.remotesLoaded) { for (const range of state.remotesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) { if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true; return true;
} }
} }
@@ -82,7 +85,11 @@ export default {
end: end, end: end,
}); });
return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end) return fetchCalendarRemoteForUser(
ctx.rootGetters["me/getMe"],
start,
end,
)
.then((remotes: CalendarRemote[]) => { .then((remotes: CalendarRemote[]) => {
// to be add when reactivity problem will be solve ? // to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes); //ctx.commit('addRemotes', remotes);

View File

@@ -112,8 +112,11 @@ export default {
results.forEach((i) => { results.forEach((i) => {
if (!users.some((j) => i.user.id === j.id)) { if (!users.some((j) => i.user.id === j.id)) {
let ratio = Math.floor(users.length / COLORS.length); let ratio = Math.floor(
let colorIndex = users.length - ratio * COLORS.length; users.length / COLORS.length,
);
let colorIndex =
users.length - ratio * COLORS.length;
users.push({ users.push({
id: i.user.id, id: i.user.id,
username: i.user.username, username: i.user.username,
@@ -150,29 +153,45 @@ export default {
(me) => (me) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
this.users.logged = me; this.users.logged = me;
let currentUser = users.find((u) => u.id === me.id); let currentUser = users.find(
(u) => u.id === me.id,
);
this.value = currentUser; this.value = currentUser;
fetchCalendar(currentUser.id).then( fetchCalendar(currentUser.id).then(
(calendar) => (calendar) =>
new Promise((resolve, reject) => { new Promise(
let results = calendar.results; (resolve, reject) => {
let events = results.map((i) => ({ let results =
start: i.startDate.datetime, calendar.results;
end: i.endDate.datetime, let events =
})); results.map(
let calendarEventsCurrentUser = { (i) => ({
start: i
.startDate
.datetime,
end: i
.endDate
.datetime,
}),
);
let calendarEventsCurrentUser =
{
events: events, events: events,
color: "darkblue", color: "darkblue",
id: 1000, id: 1000,
editable: false, editable: false,
}; };
this.calendarEvents.user = calendarEventsCurrentUser; this.calendarEvents.user =
calendarEventsCurrentUser;
this.selectUsers(currentUser); this.selectUsers(
currentUser,
);
resolve(); resolve();
}), },
),
); );
resolve(); resolve();
@@ -190,7 +209,9 @@ export default {
return `${value.username}`; return `${value.username}`;
}, },
coloriseSelectedValues() { coloriseSelectedValues() {
let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0]; let tags = document.querySelectorAll(
"div.multiselect__tags-wrap",
)[0];
if (tags.hasChildNodes()) { if (tags.hasChildNodes()) {
let children = tags.childNodes; let children = tags.childNodes;
@@ -211,8 +232,8 @@ export default {
}, },
selectEvents() { selectEvents() {
let selectedUsersId = this.users.selected.map((a) => a.id); let selectedUsersId = this.users.selected.map((a) => a.id);
this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) => this.calendarEvents.selected = this.calendarEvents.loaded.filter(
selectedUsersId.includes(a.id), (a) => selectedUsersId.includes(a.id),
); );
}, },
selectUsers(value) { selectUsers(value) {
@@ -222,7 +243,9 @@ export default {
this.updateEventsSource(); this.updateEventsSource();
}, },
unSelectUsers(value) { unSelectUsers(value) {
this.users.selected = this.users.selected.filter((a) => a.id != value.id); this.users.selected = this.users.selected.filter(
(a) => a.id != value.id,
);
this.selectEvents(); this.selectEvents();
this.updateEventsSource(); this.updateEventsSource();
}, },

View File

@@ -20,7 +20,10 @@
</option> </option>
<template v-for="t in templates" :key="t.id"> <template v-for="t in templates" :key="t.id">
<option :value="t.id"> <option :value="t.id">
{{ localizeString(t.name) || "Aucun nom défini" }} {{
localizeString(t.name) ||
"Aucun nom défini"
}}
</option> </option>
</template> </template>
</select> </select>
@@ -28,7 +31,9 @@
v-if="canGenerate" v-if="canGenerate"
class="btn btn-update btn-sm change-icon" class="btn btn-update btn-sm change-icon"
:href="buildUrlGenerate" :href="buildUrlGenerate"
@click.prevent="clickGenerate($event, buildUrlGenerate)" @click.prevent="
clickGenerate($event, buildUrlGenerate)
"
><i class="fa fa-fw fa-cog" ><i class="fa fa-fw fa-cog"
/></a> /></a>
<a <a

View File

@@ -25,6 +25,8 @@ use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
// use Symfony\Component\Translation\LocaleSwitcher;
/** /**
* @see OnGenerationFailsTest for test suite * @see OnGenerationFailsTest for test suite
*/ */
@@ -40,6 +42,7 @@ final readonly class OnGenerationFails implements EventSubscriberInterface
private StoredObjectRepositoryInterface $storedObjectRepository, private StoredObjectRepositoryInterface $storedObjectRepository,
private TranslatorInterface $translator, private TranslatorInterface $translator,
private UserRepositoryInterface $userRepository, private UserRepositoryInterface $userRepository,
// private LocaleSwitcher $localeSwitcher,
) {} ) {}
public static function getSubscribedEvents() public static function getSubscribedEvents()
@@ -118,6 +121,25 @@ final readonly class OnGenerationFails implements EventSubscriberInterface
return; 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()) $email = (new TemplatedEmail())
->to($message->getSendResultToEmail()) ->to($message->getSendResultToEmail())
->subject($this->translator->trans('docgen.failure_email.The generation of a document failed')) ->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\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
// use Symfony\Component\Translation\LocaleSwitcher;
/** /**
* Handle the request of document generation. * Handle the request of document generation.
*/ */
@@ -46,6 +48,7 @@ class RequestGenerationHandler implements MessageHandlerInterface
private readonly MailerInterface $mailer, private readonly MailerInterface $mailer,
private readonly TranslatorInterface $translator, private readonly TranslatorInterface $translator,
private readonly StoredObjectManagerInterface $storedObjectManager, private readonly StoredObjectManagerInterface $storedObjectManager,
// private readonly LocaleSwitcher $localeSwitcher,
) {} ) {}
public function __invoke(RequestGenerationMessage $message) public function __invoke(RequestGenerationMessage $message)
@@ -122,6 +125,30 @@ class RequestGenerationHandler implements MessageHandlerInterface
private function sendDataDump(StoredObject $destinationStoredObject, RequestGenerationMessage $message): void 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 // Get the content of the document
$content = $this->storedObjectManager->read($destinationStoredObject); $content = $this->storedObjectManager->read($destinationStoredObject);
$filename = $destinationStoredObject->getFilename(); $filename = $destinationStoredObject->getFilename();

View File

@@ -94,7 +94,7 @@ class StoredObject implements Document, TrackCreationInterface
/** /**
* @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion> * @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; 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\ChillDateType;
use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\ScopePickerType; use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
@@ -30,7 +29,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class PersonDocumentType extends AbstractType 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) 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']) { if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) {
$builder->add('scope', ScopePickerType::class, [ $builder->add('scope', ScopePickerType::class, [
'center' => $this->centerResolverDispatcher->resolveCenter($document),
'role' => $options['role'], 'role' => $options['role'],
'subject' => $document,
]); ]);
} }
} }

View File

@@ -13,9 +13,8 @@ const startApp = (
const inputTitle = collectionEntry?.querySelector("input[type='text']"); const inputTitle = collectionEntry?.querySelector("input[type='text']");
const input_stored_object: HTMLInputElement | null = divElement.querySelector( const input_stored_object: HTMLInputElement | null =
"input[data-stored-object]", divElement.querySelector("input[data-stored-object]");
);
if (null === input_stored_object) { if (null === input_stored_object) {
throw new Error("input to stored object not found"); throw new Error("input to stored object not found");
} }
@@ -54,7 +53,9 @@ const startApp = (
console.log("version added", stored_object_version); console.log("version added", stored_object_version);
this.$data.existingDoc = stored_object; this.$data.existingDoc = stored_object;
this.$data.existingDoc.currentVersion = stored_object_version; this.$data.existingDoc.currentVersion = stored_object_version;
input_stored_object.value = JSON.stringify(this.$data.existingDoc); input_stored_object.value = JSON.stringify(
this.$data.existingDoc,
);
if (this.$data.inputTitle) { if (this.$data.inputTitle) {
if (!this.$data.inputTitle?.value) { if (!this.$data.inputTitle?.value) {
this.$data.inputTitle.value = file_name; this.$data.inputTitle.value = file_name;

View File

@@ -49,7 +49,9 @@
<li v-if="isHistoryViewable"> <li v-if="isHistoryViewable">
<history-button <history-button
:stored-object="props.storedObject" :stored-object="props.storedObject"
:can-edit="canEdit && props.storedObject._permissions.canEdit" :can-edit="
canEdit && props.storedObject._permissions.canEdit
"
></history-button> ></history-button>
</li> </li>
</ul> </ul>
@@ -127,7 +129,9 @@ const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
canDownload: true, canDownload: true,
canConvertPdf: true, canConvertPdf: true,
returnPath: returnPath:
window.location.pathname + window.location.search + window.location.hash, window.location.pathname +
window.location.search +
window.location.hash,
}); });
/** /**

View File

@@ -29,7 +29,9 @@
</modal> </modal>
</teleport> </teleport>
<div class="col-12 m-auto sticky-top"> <div class="col-12 m-auto sticky-top">
<div class="row justify-content-center border-bottom pdf-tools d-md-none"> <div
class="row justify-content-center border-bottom pdf-tools d-md-none"
>
<div class="col-5 text-center turn-page"> <div class="col-5 text-center turn-page">
<select <select
class="form-select form-select-sm" class="form-select form-select-sm"
@@ -90,7 +92,10 @@
v-if="signature.zones.length === 1 && signedState !== 'signed'" v-if="signature.zones.length === 1 && signedState !== 'signed'"
class="col-5 p-0 text-center turnSignature" class="col-5 p-0 text-center turnSignature"
> >
<button class="btn btn-light btn-sm" @click="goToSignatureZoneUnique"> <button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }} {{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button> </button>
</div> </div>
@@ -145,7 +150,10 @@
:title="trans(SIGNATURES_ADD_SIGN_ZONE)" :title="trans(SIGNATURES_ADD_SIGN_ZONE)"
> >
<template v-if="canvasEvent === 'add'"> <template v-if="canvasEvent === 'add'">
<div class="spinner-border spinner-border-sm" role="status"> <div
class="spinner-border spinner-border-sm"
role="status"
>
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
</template> </template>
@@ -199,7 +207,10 @@
v-if="signature.zones.length === 1 && signedState !== 'signed'" v-if="signature.zones.length === 1 && signedState !== 'signed'"
class="col-4 d-xl-none text-center turnSignature p-0" class="col-4 d-xl-none text-center turnSignature p-0"
> >
<button class="btn btn-light btn-sm" @click="goToSignatureZoneUnique"> <button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }} {{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button> </button>
</div> </div>
@@ -227,7 +238,10 @@
v-if="signature.zones.length === 1 && signedState !== 'signed'" v-if="signature.zones.length === 1 && signedState !== 'signed'"
class="col-4 d-none d-xl-flex p-0 text-center turnSignature" class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
> >
<button class="btn btn-light btn-sm" @click="goToSignatureZoneUnique"> <button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }} {{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button> </button>
</div> </div>
@@ -285,7 +299,10 @@
</template> </template>
<template v-else> <template v-else>
{{ trans(SIGNATURES_CLICK_ON_DOCUMENT) }} {{ trans(SIGNATURES_CLICK_ON_DOCUMENT) }}
<div class="spinner-border spinner-border-sm" role="status"> <div
class="spinner-border spinner-border-sm"
role="status"
>
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
</template> </template>
@@ -545,8 +562,14 @@ const addCanvasEvents = () => {
); );
}); });
} else { } else {
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement; const canvas = document.querySelectorAll(
canvas.addEventListener("pointerup", (e) => canvasClick(e, canvas), false); "canvas",
)[0] as HTMLCanvasElement;
canvas.addEventListener(
"pointerup",
(e) => canvasClick(e, canvas),
false,
);
} }
}; };
@@ -582,7 +605,11 @@ const hitSignature = (
scaleYToCanvas(zone.y, canvas.height, zone.PDFPage.height) < scaleYToCanvas(zone.y, canvas.height, zone.PDFPage.height) <
xy[1] && xy[1] &&
xy[1] < xy[1] <
scaleYToCanvas(zone.height - zone.y, canvas.height, zone.PDFPage.height) + scaleYToCanvas(
zone.height - zone.y,
canvas.height,
zone.PDFPage.height,
) +
zone.PDFPage.height * zoom.value; zone.PDFPage.height * zoom.value;
const selectZone = async (z: SignatureZone, canvas: HTMLCanvasElement) => { const selectZone = async (z: SignatureZone, canvas: HTMLCanvasElement) => {
@@ -598,7 +625,8 @@ const selectZoneEvent = (e: PointerEvent, canvas: HTMLCanvasElement) =>
signature.zones signature.zones
.filter( .filter(
(z) => (z) =>
(z.PDFPage.index + 1 === getCanvasId(canvas) && multiPage.value) || (z.PDFPage.index + 1 === getCanvasId(canvas) &&
multiPage.value) ||
(z.PDFPage.index + 1 === page.value && !multiPage.value), (z.PDFPage.index + 1 === page.value && !multiPage.value),
) )
.map((z) => { .map((z) => {

View File

@@ -153,10 +153,12 @@ const handleFile = async (file: File): Promise<void> => {
</p> </p>
<!-- todo i18n --> <!-- todo i18n -->
<p v-if="has_existing_doc"> <p v-if="has_existing_doc">
Déposez un document ou cliquez ici pour remplacer le document existant Déposez un document ou cliquez ici pour remplacer le document
existant
</p> </p>
<p v-else> <p v-else>
Déposez un document ou cliquez ici pour ouvrir le navigateur de fichier Déposez un document ou cliquez ici pour ouvrir le navigateur de
fichier
</p> </p>
</div> </div>
<div v-else class="waiting"> <div v-else class="waiting">

View File

@@ -35,7 +35,9 @@ async function download_and_open(event: Event): Promise<void> {
if (null === state.content) { if (null === state.content) {
event.preventDefault(); event.preventDefault();
const raw = await download_doc(build_convert_link(props.storedObject.uuid)); const raw = await download_doc(
build_convert_link(props.storedObject.uuid),
);
state.content = window.URL.createObjectURL(raw); state.content = window.URL.createObjectURL(raw);
button.href = window.URL.createObjectURL(raw); button.href = window.URL.createObjectURL(raw);

View File

@@ -42,7 +42,9 @@ const editionUntilFormatted = computed<string>(() => {
<modal v-if="state.modalOpened" @close="state.modalOpened = false"> <modal v-if="state.modalOpened" @close="state.modalOpened = false">
<template v-slot:body> <template v-slot:body>
<div class="desktop-edit"> <div class="desktop-edit">
<p class="center">Veuillez enregistrer vos modifications avant le</p> <p class="center">
Veuillez enregistrer vos modifications avant le
</p>
<p> <p>
<strong>{{ editionUntilFormatted }}</strong> <strong>{{ editionUntilFormatted }}</strong>
</p> </p>
@@ -55,21 +57,23 @@ const editionUntilFormatted = computed<string>(() => {
<p> <p>
<small <small
>Le document peut être édité uniquement en utilisant Libre >Le document peut être édité uniquement en utilisant
Office.</small Libre Office.</small
> >
</p> </p>
<p> <p>
<small <small
>En cas d'échec lors de l'enregistrement, sauver le document sur >En cas d'échec lors de l'enregistrement, sauver le
le poste de travail avant de le déposer à nouveau ici.</small document sur le poste de travail avant de le déposer
à nouveau ici.</small
> >
</p> </p>
<p> <p>
<small <small
>Vous pouvez naviguez sur d'autres pages pendant l'édition.</small >Vous pouvez naviguez sur d'autres pages pendant
l'édition.</small
> >
</p> </p>
</div> </div>

View File

@@ -95,7 +95,10 @@ async function download_and_open(): Promise<void> {
let raw; let raw;
try { try {
raw = await download_and_decrypt_doc(props.storedObject, props.atVersion); raw = await download_and_decrypt_doc(
props.storedObject,
props.atVersion,
);
} catch (e) { } catch (e) {
console.error("error while downloading and decrypting document"); console.error("error while downloading and decrypting document");
console.error(e); console.error(e);

View File

@@ -2,7 +2,9 @@
<a <a
:class="Object.assign(props.classes, { btn: true })" :class="Object.assign(props.classes, { btn: true })"
@click="beforeLeave($event)" @click="beforeLeave($event)"
:href="build_wopi_editor_link(props.storedObject.uuid, props.returnPath)" :href="
build_wopi_editor_link(props.storedObject.uuid, props.returnPath)
"
> >
<i class="fa fa-paragraph"></i> <i class="fa fa-paragraph"></i>
Editer en ligne Editer en ligne

View File

@@ -145,7 +145,9 @@ async function download_info_link(
function build_wopi_editor_link(uuid: string, returnPath?: string) { function build_wopi_editor_link(uuid: string, returnPath?: string) {
if (returnPath === undefined) { if (returnPath === undefined) {
returnPath = returnPath =
window.location.pathname + window.location.search + window.location.hash; window.location.pathname +
window.location.search +
window.location.hash;
} }
return ( return (
@@ -175,6 +177,8 @@ async function download_and_decrypt_doc(
throw new Error("no version associated to stored object"); throw new Error("no version associated to stored object");
} }
// sometimes, the downloadInfo may be embedded into the storedObject
console.log("storedObject", storedObject);
let downloadInfo; let downloadInfo;
if ( if (
typeof storedObject._links !== "undefined" && typeof storedObject._links !== "undefined" &&
@@ -182,7 +186,10 @@ async function download_and_decrypt_doc(
) { ) {
downloadInfo = storedObject._links.downloadLink; downloadInfo = storedObject._links.downloadLink;
} else { } else {
downloadInfo = await download_info_link(storedObject, atVersionToDownload); downloadInfo = await download_info_link(
storedObject,
atVersionToDownload,
);
} }
const rawResponse = await window.fetch(downloadInfo.url); const rawResponse = await window.fetch(downloadInfo.url);
@@ -237,7 +244,10 @@ async function download_doc_as_pdf(storedObject: StoredObject): Promise<Blob> {
} }
if (storedObject.currentVersion?.type === "application/pdf") { if (storedObject.currentVersion?.type === "application/pdf") {
return download_and_decrypt_doc(storedObject, storedObject.currentVersion); return download_and_decrypt_doc(
storedObject,
storedObject.currentVersion,
);
} }
const convertLink = build_convert_link(storedObject.uuid); const convertLink = build_convert_link(storedObject.uuid);

View File

@@ -43,17 +43,11 @@ class StoredObjectVersionNormalizer implements NormalizerInterface, NormalizerAw
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, [...$context, UserNormalizer::AT_DATE => $object->getCreatedAt()]), 'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, [...$context, UserNormalizer::AT_DATE => $object->getCreatedAt()]),
]; ];
$normalizationGroups = $context[AbstractNormalizer::GROUPS] ?? []; if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
if (is_string($normalizationGroups)) {
$normalizationGroups = [$normalizationGroups];
}
if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $normalizationGroups, true)) {
$data['point-in-times'] = $this->normalizer->normalize($object->getPointInTimes(), $format, $context); $data['point-in-times'] = $this->normalizer->normalize($object->getPointInTimes(), $format, $context);
} }
if (in_array(self::WITH_RESTORED_CONTEXT, $normalizationGroups, true)) { if (in_array(self::WITH_RESTORED_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
$data['from-restored'] = $this->normalizer->normalize($object->getCreatedFrom(), $format, [AbstractNormalizer::GROUPS => ['read']]); $data['from-restored'] = $this->normalizer->normalize($object->getCreatedFrom(), $format, [AbstractNormalizer::GROUPS => ['read']]);
} }

View File

@@ -50,7 +50,7 @@
<ul class="record_actions"> <ul class="record_actions">
<li> <li>
<a href="{{ path('chill_event__event_show', { 'event_id' : event.id } ) }}" class="btn btn-cancel"> <a href="{{ path('chill_event__event_show', { 'id' : event.id } ) }}" class="btn btn-cancel">
{{ 'Back to the event'|trans }} {{ 'Back to the event'|trans }}
</a> </a>
</li> </li>

View File

@@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\NotificationFlagManager; use Chill\MainBundle\Notification\NotificationFlagManager;
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint; use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumber;
use Symfony\Component\Validator\Constraints as Assert;
final class UpdateProfileCommand final class UpdateProfileCommand
{ {
@@ -23,11 +24,13 @@ final class UpdateProfileCommand
public function __construct( public function __construct(
#[PhonenumberConstraint] #[PhonenumberConstraint]
public ?PhoneNumber $phonenumber, public ?PhoneNumber $phonenumber,
#[Assert\Choice(choices: ['fr', 'nl'], message: 'Locale must be either "fr" or "nl"')]
public string $locale = 'fr',
) {} ) {}
public static function create(User $user, NotificationFlagManager $flagManager): self public static function create(User $user, NotificationFlagManager $flagManager): self
{ {
$updateProfileCommand = new self($user->getPhonenumber()); $updateProfileCommand = new self($user->getPhonenumber(), $user->getLocale());
foreach ($flagManager->getAllNotificationFlagProviders() as $provider) { foreach ($flagManager->getAllNotificationFlagProviders() as $provider) {
$updateProfileCommand->setNotificationFlag( $updateProfileCommand->setNotificationFlag(

View File

@@ -18,6 +18,7 @@ final readonly class UpdateProfileCommandHandler
public function updateProfile(User $user, UpdateProfileCommand $command): void public function updateProfile(User $user, UpdateProfileCommand $command): void
{ {
$user->setPhonenumber($command->phonenumber); $user->setPhonenumber($command->phonenumber);
$user->setLocale($command->locale);
foreach ($command->notificationFlags as $flag => $values) { foreach ($command->notificationFlags as $flag => $values) {
$user->setNotificationImmediately($flag, $values['immediate_email']); $user->setNotificationImmediately($flag, $values['immediate_email']);

View File

@@ -14,6 +14,7 @@ namespace Chill\MainBundle;
use Chill\MainBundle\Cron\CronJobInterface; use Chill\MainBundle\Cron\CronJobInterface;
use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass; use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\TimelineCompilerClass; use Chill\MainBundle\DependencyInjection\CompilerPass\TimelineCompilerClass;
@@ -69,6 +70,7 @@ class ChillMainBundle extends Bundle
$container->addCompilerPass(new TimelineCompilerClass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new TimelineCompilerClass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->addCompilerPass(new WidgetsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new WidgetsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->addCompilerPass(new NotificationCounterCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new NotificationCounterCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->addCompilerPass(new MenuCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->addCompilerPass(new ACLFlagsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new ACLFlagsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->addCompilerPass(new CRUDControllerCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new CRUDControllerCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
} }

View File

@@ -1,115 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
use Symfony\Component\Yaml\Yaml;
class OverrideTranslationCommand extends Command
{
public function __construct(
private readonly TranslationReaderInterface $reader,
private readonly TranslationWriterInterface $writer,
) {
$this->setName('chill:main:override_translation');
parent::__construct();
}
protected function configure(): void
{
$this
->setDescription('Generate a translation catalogue with translation remplacements based on replacements provided in a YAML file.')
->addArgument('locale', InputArgument::REQUIRED, 'The locale to process (e.g. fr, en).')
->addArgument('overrides', InputArgument::REQUIRED, 'Path to the overrides YAML file (list of {from, to}).');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$locale = (string) $input->getArgument('locale');
$overridesPath = (string) $input->getArgument('overrides');
$catalogue = $this->loadCatalogue($locale);
$overrides = $this->loadOverrides($overridesPath);
$toOverrideCatalogue = new MessageCatalogue($locale);
foreach ($catalogue->getDomains() as $domain) {
// hack: we have to replace the suffix ".intl-icu" by "+intl-ic"
$domain = str_replace('.intl-icu', '+intl-icu', $domain);
foreach ($catalogue->all($domain) as $key => $translation) {
foreach ($overrides as $changes) {
$from = $changes['from'];
$to = $changes['to'];
if (is_string($translation) && str_contains($translation, $from)) {
$newTranslation = strtr($translation, [$from => $to]);
$toOverrideCatalogue->set($key, $newTranslation, $domain);
$translation = $newTranslation;
}
}
}
}
/** @var KernelInterface $kernel */
/* @phpstan-ignore-next-line */
$kernel = $this->getApplication()->getKernel();
$outputDir = rtrim($kernel->getProjectDir(), '/').'/translations';
if (!is_dir($outputDir)) {
@mkdir($outputDir, 0775, true);
}
// Writer expects the 'path' option to be a directory; it will create the proper file name
$this->writer->write($toOverrideCatalogue, 'yaml', ['path' => $outputDir]);
$output->writeln(sprintf('Override catalogue written to %s (domain: messages, locale: %s).', $outputDir, $locale));
return Command::SUCCESS;
}
/**
* @return list<array{from: string, to: string}>
*/
private function loadOverrides(string $path): array
{
return Yaml::parseFile($path);
}
private function loadCatalogue(string $locale): MessageCatalogue
{
/** @var KernelInterface $kernel */
/* @phpstan-ignore-next-line */
$kernel = $this->getApplication()->getKernel();
// collect path for translations
$transPaths = [];
foreach ($kernel->getBundles() as $bundle) {
$bundleDir = $bundle->getPath();
$transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations';
}
$currentCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
if (is_dir($path)) {
$this->reader->read($path, $currentCatalogue);
}
}
return $currentCatalogue;
}
}

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller; namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
class UserGroupApiController extends ApiController {} class UserGroupApiController extends ApiController
{
protected function customizeQuery(string $action, Request $request, $query): void
{
$query->andWhere('e.active = TRUE');
}
}

View File

@@ -128,6 +128,12 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]', 'jsonb' => true])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]', 'jsonb' => true])]
private array $notificationFlags = []; private array $notificationFlags = [];
/**
* User's preferred locale.
*/
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 5, nullable: false, options: ['default' => 'fr'])]
private string $locale = 'fr';
/** /**
* User constructor. * User constructor.
*/ */
@@ -716,7 +722,14 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
public function getLocale(): string public function getLocale(): string
{ {
return 'fr'; return $this->locale;
}
public function setLocale(string $locale): self
{
$this->locale = $locale;
return $this;
} }
#[Assert\Callback] #[Assert\Callback]

View File

@@ -27,8 +27,6 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
/** /**
* Create a CSV List for the export. * Create a CSV List for the export.
*/ */

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper; use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
@@ -32,65 +33,84 @@ use Symfony\Component\Security\Core\Security;
* Allow to pick amongst available scope for the current * Allow to pick amongst available scope for the current
* user. * user.
* *
* options : * Options:
* * - `role`: string, the role to check permissions for
* - `center`: the center of the entity * - Either `subject`: object, entity to resolve centers from
* - `role` : the role of the user * - Or `center`: Center|array|null, the center(s) to check
*/ */
class ScopePickerType extends AbstractType class ScopePickerType extends AbstractType
{ {
public function __construct( public function __construct(
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly AuthorizationHelperInterface $authorizationHelper, private readonly AuthorizationHelperInterface $authorizationHelper,
private readonly Security $security, private readonly Security $security,
private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly CenterResolverManagerInterface $centerResolverManager,
) {} ) {}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$items = array_values( // Compute centers from subject
$centers = $options['center'] ?? null;
if (null === $centers && isset($options['subject'])) {
$centers = $this->centerResolverManager->resolveCenters($options['subject']);
}
if (null === $centers) {
throw new \RuntimeException('Either "center" or "subject" must be provided');
}
$reachableScopes = array_values(
array_filter( array_filter(
$this->authorizationHelper->getReachableScopes( $this->authorizationHelper->getReachableScopes(
$this->security->getUser(), $this->security->getUser(),
$options['role'], $options['role'],
$options['center'] $centers
), ),
static fn (Scope $s) => $s->isActive() static fn (Scope $s) => $s->isActive()
) )
); );
if (0 === \count($items)) { $builder->setAttribute('reachable_scopes_count', count($reachableScopes));
throw new \RuntimeException('no scopes are reachable. This form should not be shown to user');
if (0 === count($reachableScopes)) {
$builder->setAttribute('has_scopes', false);
return;
} }
if (1 !== \count($items)) { $builder->setAttribute('has_scopes', true);
if (1 !== count($reachableScopes)) {
$builder->add('scope', EntityType::class, [ $builder->add('scope', EntityType::class, [
'class' => Scope::class, 'class' => Scope::class,
'placeholder' => 'Choose the circle', 'placeholder' => 'Choose the circle',
'choice_label' => fn (Scope $c) => $this->translatableStringHelper->localize($c->getName()), 'choice_label' => fn (Scope $c) => $this->translatableStringHelper->localize($c->getName()),
'choices' => $items, 'choices' => $reachableScopes,
]); ]);
$builder->setDataMapper(new ScopePickerDataMapper()); $builder->setDataMapper(new ScopePickerDataMapper());
} else { } else {
$builder->add('scope', HiddenType::class, [ $builder->add('scope', HiddenType::class, [
'data' => $items[0]->getId(), 'data' => $reachableScopes[0]->getId(),
]); ]);
$builder->setDataMapper(new ScopePickerDataMapper($items[0])); $builder->setDataMapper(new ScopePickerDataMapper($reachableScopes[0]));
} }
} }
public function buildView(FormView $view, FormInterface $form, array $options) public function buildView(FormView $view, FormInterface $form, array $options)
{ {
$view->vars['fullWidth'] = true; $view->vars['fullWidth'] = true;
// display of label is handled by the EntityType
$view->vars['label'] = false;
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$resolver $resolver
// create `center` option
->setRequired('center')
->setAllowedTypes('center', [Center::class, 'array', 'null'])
// create ``role` option
->setRequired('role') ->setRequired('role')
->setAllowedTypes('role', ['string']); ->setAllowedTypes('role', ['string'])
->setDefined('subject')
->setAllowedTypes('subject', ['object'])
->setDefined('center')
->setAllowedTypes('center', [Center::class, 'array', 'null']);
} }
} }

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Intl\Languages;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserLocaleType extends AbstractType
{
public function __construct(private readonly array $availableLanguages) {}
public function configureOptions(OptionsResolver $resolver): void
{
$choices = [];
foreach ($this->availableLanguages as $languageCode) {
$choices[Languages::getName($languageCode)] = $languageCode;
}
$resolver->setDefaults([
'choices' => $choices,
'placeholder' => 'user.locale.placeholder',
'required' => true,
'label' => 'user.locale.label',
'help' => 'user.locale.help',
]);
}
public function getParent(): string
{
return ChoiceType::class;
}
}

View File

@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Form;
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommand; use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommand;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType; use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Form\Type\NotificationFlagsType; use Chill\MainBundle\Form\Type\NotificationFlagsType;
use Chill\MainBundle\Form\Type\UserLocaleType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -26,6 +27,7 @@ class UpdateProfileType extends AbstractType
->add('phonenumber', ChillPhoneNumberType::class, [ ->add('phonenumber', ChillPhoneNumberType::class, [
'required' => false, 'required' => false,
]) ])
->add('locale', UserLocaleType::class)
->add('notificationFlags', NotificationFlagsType::class) ->add('notificationFlags', NotificationFlagsType::class)
; ;
} }

View File

@@ -24,6 +24,8 @@ use Symfony\Component\Mime\Email;
use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
// use Symfony\Component\Translation\LocaleSwitcher;
readonly class NotificationMailer readonly class NotificationMailer
{ {
public function __construct( public function __construct(
@@ -31,6 +33,7 @@ readonly class NotificationMailer
private LoggerInterface $logger, private LoggerInterface $logger,
private MessageBusInterface $messageBus, private MessageBusInterface $messageBus,
private TranslatorInterface $translator, private TranslatorInterface $translator,
// private LocaleSwitcher $localeSwitcher,
) {} ) {}
public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void
@@ -56,7 +59,7 @@ readonly class NotificationMailer
$email $email
->to($dest->getEmail()) ->to($dest->getEmail())
->subject('Re: '.$comment->getNotification()->getTitle()) ->subject('Re: '.$comment->getNotification()->getTitle())
->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig') ->textTemplate('@ChillMain/Notification/email_notification_comment_persist.md.twig')
->context([ ->context([
'comment' => $comment, 'comment' => $comment,
'dest' => $dest, 'dest' => $dest,
@@ -137,13 +140,53 @@ readonly class NotificationMailer
return; return;
} }
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
/*
$this->localeSwitcher->runWithLocale($addressee->getLocale(), function () use ($notification, $addressee) {
if ($notification->isSystem()) { if ($notification->isSystem()) {
$email = new Email(); $email = new Email();
$email->text($notification->getMessage()); $email->text($notification->getMessage());
} else { } else {
$email = new TemplatedEmail(); $email = new TemplatedEmail();
$email $email
->textTemplate('@ChillMain/Notification/email_non_system_notification_content.fr.md.twig') ->textTemplate('@ChillMain/Notification/email_non_system_notification_content.md.twig')
->context([
'notification' => $notification,
'dest' => $addressee,
]);
}
$email
->subject($notification->getTitle())
->to($addressee->getEmail());
try {
$this->mailer->send($email);
$this->logger->info('[NotificationMailer] Email sent successfully', [
'notification_id' => $notification->getId(),
'addressee_email' => $addressee->getEmail(),
'locale' => $addressee->getLocale(),
]);
} catch (TransportExceptionInterface $e) {
$this->logger->warning('[NotificationMailer] Could not send an email notification', [
'to' => $addressee->getEmail(),
'notification_id' => $notification->getId(),
'error_message' => $e->getMessage(),
'error_trace' => $e->getTraceAsString(),
]);
throw $e;
}
});
*/
// Current implementation:
if ($notification->isSystem()) {
$email = new Email();
$email->text($notification->getMessage());
} else {
$email = new TemplatedEmail();
$email
->textTemplate('@ChillMain/Notification/email_non_system_notification_content.md.twig')
->context([ ->context([
'notification' => $notification, 'notification' => $notification,
'dest' => $addressee, 'dest' => $addressee,
@@ -182,9 +225,43 @@ readonly class NotificationMailer
return; return;
} }
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
/*
$this->localeSwitcher->runWithLocale($user->getLocale(), function () use ($user, $notifications) {
$email = new TemplatedEmail(); $email = new TemplatedEmail();
$email $email
->htmlTemplate('@ChillMain/Notification/email_daily_digest.fr.md.twig') ->htmlTemplate('@ChillMain/Notification/email_daily_digest.md.twig')
->context([
'user' => $user,
'notifications' => $notifications,
'notification_count' => count($notifications),
])
->subject($this->translator->trans('notification.Daily Notification Digest'))
->to($user->getEmail());
try {
$this->mailer->send($email);
$this->logger->info('[NotificationMailer] Daily digest email sent successfully', [
'user_email' => $user->getEmail(),
'notification_count' => count($notifications),
'locale' => $user->getLocale(),
]);
} catch (TransportExceptionInterface $e) {
$this->logger->warning('[NotificationMailer] Could not send daily digest email', [
'to' => $user->getEmail(),
'notification_count' => count($notifications),
'error_message' => $e->getMessage(),
'error_trace' => $e->getTraceAsString(),
]);
throw $e;
}
});
*/
// Current implementation:
$email = new TemplatedEmail();
$email
->htmlTemplate('@ChillMain/Notification/email_daily_digest.md.twig')
->context([ ->context([
'user' => $user, 'user' => $user,
'notifications' => $notifications, 'notifications' => $notifications,
@@ -222,7 +299,7 @@ readonly class NotificationMailer
$email = new TemplatedEmail(); $email = new TemplatedEmail();
$email $email
->textTemplate('@ChillMain/Notification/email_non_system_notification_content_to_email.fr.md.twig') ->textTemplate('@ChillMain/Notification/email_non_system_notification_content_to_email.md.twig')
->context([ ->context([
'notification' => $notification, 'notification' => $notification,
'dest' => $emailAddress, 'dest' => $emailAddress,

View File

@@ -76,24 +76,6 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
->formatOutOfCountryCallingNumber($phoneNumber, $this->config['default_carrier_code']); ->formatOutOfCountryCallingNumber($phoneNumber, $this->config['default_carrier_code']);
} }
/**
* @throws NumberParseException
*/
public function parse(string $phoneNumber): PhoneNumber
{
$sanitizedPhoneNumber = $phoneNumber;
if (str_starts_with($sanitizedPhoneNumber, '00')) {
$sanitizedPhoneNumber = '+'.substr($sanitizedPhoneNumber, 2, null);
}
if (!str_starts_with($sanitizedPhoneNumber, '+') && !str_starts_with($sanitizedPhoneNumber, '0')) {
$sanitizedPhoneNumber = '+'.$sanitizedPhoneNumber;
}
return $this->phoneNumberUtil->parse($sanitizedPhoneNumber, $this->config['default_carrier_code']);
}
/** /**
* Get type (mobile, landline, ...) for phone number. * Get type (mobile, landline, ...) for phone number.
*/ */

View File

@@ -5,16 +5,11 @@ export type fetchOption = Record<string, boolean | string | number | null>;
export type Params = Record<string, number | string>; export type Params = Record<string, number | string>;
export interface Pagination {
first: number;
items_per_page: number;
more: boolean;
next: string | null;
previous: string | null;
}
export interface PaginationResponse<T> { export interface PaginationResponse<T> {
pagination: Pagination; pagination: {
more: boolean;
items_per_page: number;
};
results: T[]; results: T[];
count: number; count: number;
} }

View File

@@ -1,5 +1,4 @@
import { DateTime, TranslatableString } from "ChillMainAssets/types"; import { TranslatableString } from "ChillMainAssets/types";
import { getLocale } from "translator";
/** /**
* Localizes a translatable string object based on the current locale. * Localizes a translatable string object based on the current locale.
@@ -9,25 +8,6 @@ import { getLocale } from "translator";
* @param locale defaults to browser locale * @param locale defaults to browser locale
* @returns The localized string or null if no translation is available * @returns The localized string or null if no translation is available
*/ */
/**
* Prepends the current HTML lang code to the given URL.
* Example: If lang="fr" and url="/about", returns "/fr/about"
*
* @param url The URL to localize
* @returns The localized URL
*/
export function localizedUrl(url: string): string {
const locale = getLocale();
// Ensure url starts with a slash and does not already start with /{lang}/
const normalizedUrl = url.startsWith("/") ? url : `/${url}`;
const langPrefix = `/${locale}`;
if (normalizedUrl.startsWith(langPrefix + "/")) {
return normalizedUrl;
}
return `${langPrefix}${normalizedUrl}`;
}
export function localizeString( export function localizeString(
translatableString: TranslatableString | null | undefined, translatableString: TranslatableString | null | undefined,
locale?: string, locale?: string,
@@ -36,7 +16,7 @@ export function localizeString(
return ""; return "";
} }
const currentLocale = locale || getLocale(); const currentLocale = locale || navigator.language.split("-")[0] || "fr";
if (translatableString[currentLocale]) { if (translatableString[currentLocale]) {
return translatableString[currentLocale]; return translatableString[currentLocale];
@@ -59,47 +39,3 @@ export function localizeString(
return ""; return "";
} }
const datetimeFormats: Record<
string,
Record<string, Intl.DateTimeFormatOptions>
> = {
fr: {
short: {
year: "numeric",
month: "numeric",
day: "numeric",
},
text: {
year: "numeric",
month: "long",
day: "numeric",
},
long: {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
hour12: false,
},
hoursOnly: {
hour: "numeric",
minute: "numeric",
hour12: false,
},
},
};
export function localizeDateTimeFormat(
dateTime: DateTime,
format: keyof typeof datetimeFormats.fr = "short",
): string {
const locale = getLocale();
const options =
datetimeFormats[locale]?.[format] || datetimeFormats.fr[format];
return new Intl.DateTimeFormat(locale, options).format(
new Date(dateTime.datetime),
);
}
export default datetimeFormats;

View File

@@ -126,8 +126,7 @@ function loadDynamicPicker(element) {
-1 === -1 ===
this.suggested.findIndex( this.suggested.findIndex(
(e) => e.type === entity.type && e.id === entity.id, (e) => e.type === entity.type && e.id === entity.id,
) && )
"me" !== entity
) { ) {
this.suggested.push(entity); this.suggested.push(entity);
} }

View File

@@ -0,0 +1,15 @@
import { createApp } from "vue";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import { appMessages } from "ChillMainAssets/vuejs/HomepageWidget/js/i18n";
import { store } from "ChillMainAssets/vuejs/HomepageWidget/js/store";
import App from "ChillMainAssets/vuejs/HomepageWidget/App";
const i18n = _createI18n(appMessages);
const app = createApp({
template: `<app></app>`,
})
.use(store)
.use(i18n)
.component("app", App)
.mount("#homepage_widget");

View File

@@ -1,12 +0,0 @@
import App from "ChillMainAssets/vuejs/HomepageWidget/App.vue";
import { createApp } from "vue";
import { store } from "ChillMainAssets/vuejs/HomepageWidget/store";
declare global {
interface Window {
homepage_config: string;
}
}
const _app = createApp(App);
_app.use(store).mount("#homepage_widget");

View File

@@ -15,11 +15,6 @@ export interface Civility {
// TODO // TODO
} }
export interface Household {
type: "household";
id: number;
}
export interface Job { export interface Job {
id: number; id: number;
type: "user_job"; type: "user_job";
@@ -296,86 +291,6 @@ export interface PrivateCommentEmbeddable {
comments: Record<number, string>; comments: Record<number, string>;
} }
// API Exception types
export interface TransportExceptionInterface {
name: string;
}
export interface ValidationExceptionInterface
extends TransportExceptionInterface {
name: "ValidationException";
error: object;
violations: string[];
titles: string[];
propertyPaths: string[];
}
export interface AccessExceptionInterface extends TransportExceptionInterface {
name: "AccessException";
violations: string[];
}
export interface NotFoundExceptionInterface
extends TransportExceptionInterface {
name: "NotFoundException";
}
export interface ServerExceptionInterface extends TransportExceptionInterface {
name: "ServerException";
message: string;
code: number;
body: string;
}
export interface ConflictHttpExceptionInterface
extends TransportExceptionInterface {
name: "ConflictHttpException";
violations: string[];
}
export type ApiException =
| ValidationExceptionInterface
| AccessExceptionInterface
| NotFoundExceptionInterface
| ServerExceptionInterface
| ConflictHttpExceptionInterface;
export interface Modal {
showModal: boolean;
modalDialogClass: string;
}
export interface Selected {
result: UserGroupOrUser;
}
export interface addNewEntities {
selected: Selected[];
modal: Modal;
}
export enum HomepageTabs {
MyCustoms,
MyTickets,
MyNotifications,
MyAccompanyingCourses,
MyEvaluations,
MyTasks,
MyWorkflows,
}
export interface HomepageConfig {
defaultTab: HomepageTabs;
displayTabs: HomepageTabs[];
}
export interface TabDefinition {
key: HomepageTabs;
label: string;
icon: string | null;
counter: () => number;
}
/** /**
* Possible states for the WaitingScreen Component. * Possible states for the WaitingScreen Component.
*/ */

View File

@@ -148,7 +148,10 @@
</template> </template>
<template #action> <template #action>
<li v-if="!this.context.edit && this.useDatePane"> <li v-if="!this.context.edit && this.useDatePane">
<button class="btn btn-update change-icon" @click="closeEditPane"> <button
class="btn btn-update change-icon"
@click="closeEditPane"
>
{{ trans(NEXT) }} {{ trans(NEXT) }}
<i class="fa fa-fw fa-arrow-right" /> <i class="fa fa-fw fa-arrow-right" />
</button> </button>
@@ -274,7 +277,8 @@ export default {
PREVIOUS, PREVIOUS,
NEXT, NEXT,
}; };
},props: ["context", "options", "addressChangedCallback"], },
props: ["context", "options", "addressChangedCallback"],
components: { components: {
Modal, Modal,
ShowPane, ShowPane,
@@ -390,7 +394,8 @@ export default {
getTextTitle() { getTextTitle() {
if ( if (
typeof this.options.title !== "undefined" && typeof this.options.title !== "undefined" &&
(this.options.title.edit !== null || this.options.title.create !== null) (this.options.title.edit !== null ||
this.options.title.create !== null)
) { ) {
console.log("this.options.title", this.options.title); console.log("this.options.title", this.options.title);
@@ -510,7 +515,10 @@ export default {
if (!this.context.edit) { if (!this.context.edit) {
this.context.edit = true; this.context.edit = true;
this.context.addressId = params.addressId; this.context.addressId = params.addressId;
console.log("context is now edit, with address", params.addressId); console.log(
"context is now edit, with address",
params.addressId,
);
} }
} }
}, },
@@ -637,7 +645,9 @@ export default {
? this.entity.address.confidential ? this.entity.address.confidential
: false; : false;
this.entity.selected.isNoAddress = this.entity.selected.isNoAddress =
this.context.edit && this.entity.address.text === "" ? true : false; this.context.edit && this.entity.address.text === ""
? true
: false;
this.entity.selected.country = this.context.edit this.entity.selected.country = this.context.edit
? this.entity.address.country ? this.entity.address.country
@@ -732,7 +742,8 @@ export default {
// add the address reference, if any // add the address reference, if any
if (this.entity.selected.address.addressReference !== undefined) { if (this.entity.selected.address.addressReference !== undefined) {
newAddress = Object.assign(newAddress, { newAddress = Object.assign(newAddress, {
addressReference: this.entity.selected.address.addressReference, addressReference:
this.entity.selected.address.addressReference,
}); });
} else { } else {
newAddress = Object.assign(newAddress, { newAddress = Object.assign(newAddress, {
@@ -752,7 +763,10 @@ export default {
}); });
} }
if (this.validTo && null !== this.entity.selected.valid.to) { if (this.validTo && null !== this.entity.selected.valid.to) {
console.log("add validTo in fetch body", this.entity.selected.valid.to); console.log(
"add validTo in fetch body",
this.entity.selected.valid.to,
);
newAddress = Object.assign(newAddress, { newAddress = Object.assign(newAddress, {
validTo: { validTo: {
datetime: `${this.entity.selected.valid.to.toISOString().split("T")[0]}T00:00:00+0100`, datetime: `${this.entity.selected.valid.to.toISOString().split("T")[0]}T00:00:00+0100`,
@@ -764,7 +778,10 @@ export default {
newPostcode = Object.assign(newPostcode, { newPostcode = Object.assign(newPostcode, {
country: { id: this.entity.selected.country.id }, country: { id: this.entity.selected.country.id },
}); //TODO why not assign postcodeBody here = Object.assign(postcodeBody, {'origin': 3}); ? }); //TODO why not assign postcodeBody here = Object.assign(postcodeBody, {'origin': 3}); ?
console.log("writeNew postcode is true! newPostcode: ", newPostcode); console.log(
"writeNew postcode is true! newPostcode: ",
newPostcode,
);
newAddress = Object.assign(newAddress, { newAddress = Object.assign(newAddress, {
newPostcode: newPostcode, newPostcode: newPostcode,
}); });

View File

@@ -72,7 +72,10 @@ export default {
this.entity.addressMap.zoom, this.entity.addressMap.zoom,
); );
} else { } else {
this.map.setView(lonLatForLeaflet(this.addressPoint.coordinates), 15); this.map.setView(
lonLatForLeaflet(this.addressPoint.coordinates),
15,
);
} }
this.map.scrollWheelZoom.disable(); this.map.scrollWheelZoom.disable();
@@ -102,7 +105,9 @@ export default {
}, },
update() { update() {
if (this.marker && this.entity.addressMap.center) { if (this.marker && this.entity.addressMap.center) {
this.marker.setLatLng(lonLatForLeaflet(this.entity.addressMap.center)); this.marker.setLatLng(
lonLatForLeaflet(this.entity.addressMap.center),
);
this.map.panTo(lonLatForLeaflet(this.entity.addressMap.center)); this.map.panTo(lonLatForLeaflet(this.entity.addressMap.center));
} }
}, },

View File

@@ -117,7 +117,8 @@ export default {
ADDRESS_FILL_AN_ADDRESS, ADDRESS_FILL_AN_ADDRESS,
trans, trans,
}; };
},props: ["entity", "isNoAddress"], },
props: ["entity", "isNoAddress"],
computed: { computed: {
floor: { floor: {
set(value) { set(value) {

View File

@@ -96,10 +96,13 @@ export default {
ADDRESS_CREATE_ADDRESS, ADDRESS_CREATE_ADDRESS,
trans, trans,
}; };
},props: ["entity", "context", "updateMapCenter", "flag", "checkErrors"], },
props: ["entity", "context", "updateMapCenter", "flag", "checkErrors"],
data() { data() {
return { return {
value: this.context.edit ? this.entity.address.addressReference : null, value: this.context.edit
? this.entity.address.addressReference
: null,
isLoading: false, isLoading: false,
}; };
}, },
@@ -172,7 +175,8 @@ export default {
.then( .then(
(addresses) => (addresses) =>
new Promise((resolve) => { new Promise((resolve) => {
this.entity.loaded.addresses = addresses.results; this.entity.loaded.addresses =
addresses.results;
this.isLoading = false; this.isLoading = false;
resolve(); resolve();
}), }),
@@ -189,7 +193,8 @@ export default {
.then( .then(
(addresses) => (addresses) =>
new Promise((resolve) => { new Promise((resolve) => {
this.entity.loaded.addresses = addresses.results; this.entity.loaded.addresses =
addresses.results;
this.isLoading = false; this.isLoading = false;
resolve(); resolve();
}), }),

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