Compare commits

...

30 Commits

Author SHA1 Message Date
499aa3adea PHP cs fixes 2025-02-26 11:12:05 +01:00
b597ff89e0 Fix StoredObjectApiControllerTest.php to match new dependency injection 2025-02-26 11:11:57 +01:00
3c801a6d0e Fix linter issues 2025-02-26 11:11:09 +01:00
4093949c2f Fix inject ManagerRegistry as dependency 2025-02-25 13:32:13 +01:00
7ed10efcd1 Fix declaration of variables for dynamic picker 2025-02-24 13:49:33 +01:00
350661a4fa Merge branch '341-export-csv-thirdparties' into 'master'
Resolve "Extraction de la liste des tiers au format CSV"

Closes #341

See merge request Chill-Projet/chill-bundles!789
2025-02-19 11:37:46 +00:00
08207b656a Resolve "Extraction de la liste des tiers au format CSV" 2025-02-19 11:37:46 +00:00
8de63de6d6 Merge branch '343-social_issue_action_export' into 'master'
Resolve "Permettre de télécharger la liste des problématiques et la liste des actions en CSV"

Closes #343

See merge request Chill-Projet/chill-bundles!785
2025-02-19 11:18:05 +00:00
51804b10c0 Resolve "Permettre de télécharger la liste des problématiques et la liste des actions en CSV" 2025-02-19 11:18:04 +00:00
02f555efae Merge branch '333-working-with-translations' into 'master'
Setup alias for use in standalone chill-bundles project and replace relative paths

Closes #333

See merge request Chill-Projet/chill-bundles!766
2025-02-19 10:57:59 +00:00
d2fcb6945b Setup alias for use in standalone chill-bundles project and replace relative paths 2025-02-19 10:57:59 +00:00
c89e3785ef Merge branch '360-restore-document-on-workflow-cancel' into 'master'
Add event subscriber for document restoration on cancel

Closes #360

See merge request Chill-Projet/chill-bundles!798
2025-02-17 12:22:57 +00:00
9f17ec4841 Merge branch '353-doc_require_to_populate_languages' into 'master'
doc: add languages population command

Closes #353

See merge request Chill-Projet/chill-bundles!792
2025-02-17 12:22:25 +00:00
b277a7749a Merge branch '357-doc_address-ref-requires-language' into 'master'
doc: chill:main:address-ref-from-best-addresse requires a language code

Closes #357

See merge request Chill-Projet/chill-bundles!793
2025-02-17 12:21:48 +00:00
c8b6b6e33a Merge branch '349-suggest-agents-traitants' into 'master'
Resolve "Proposer en plus du référent de parcours les agents traitants saisis dans toutes les cations d'accompagnement du parcours"

Closes #349

See merge request Chill-Projet/chill-bundles!787
2025-02-17 12:21:10 +00:00
10eaebf610 Resolve "Proposer en plus du référent de parcours les agents traitants saisis dans toutes les cations d'accompagnement du parcours" 2025-02-17 12:21:10 +00:00
0a34f9086f Add event subscriber for document restoration on cancel
Implement an event subscriber to restore documents to their last kept version when a workflow transition ends in a non-positive final state. Includes corresponding unit tests and an unreleased feature change log entry.
2025-02-14 15:08:42 +01:00
739e0b1692 Merge branch 'fix/docgen-accompanying-period-context-with-list-aworks' into 'master'
Refactor username mapping in activity context filter.

See merge request Chill-Projet/chill-bundles!797
2025-02-14 12:20:07 +00:00
8db8f5fdf5 Refactor username mapping in activity context filter.
Replaced array_map with a foreach loop for clarity and maintainability when extracting usernames from work referrers. This ensures better readability and aligns with coding standards.
2025-02-14 12:11:28 +01:00
fc32f9eca9 Merge branch 'add-dependents' into 'master'
add household properties :  number of dependents and number of dependents with disabilities

See merge request Chill-Projet/chill-bundles!778
2025-02-13 12:41:29 +00:00
ab35e8c034 Release v3.8.2 2025-02-10 14:52:22 +01:00
2aded2974f Merge branch '358-remove-filter-button' into 'master'
Remove "filter" button from attachment modal in workflows

Closes #358

See merge request Chill-Projet/chill-bundles!794
2025-02-10 13:49:09 +00:00
f84c1632b2 Remove "filter" button from attachment modal in workflows
The "filter" button was unnecessary in the document list within the "add attachment" modal and has been removed for simplicity. This change does not involve any schema modifications and resolves issue #358.
2025-02-10 14:41:53 +01:00
Christophe Siraut
03717a1a87 ChillPersonBundle: move numberOfDependents configuration to a new household node; extend AdministrativeStatusRepository and EmploymentStatusRepository from ServiceEntityRepository 2025-02-07 17:45:33 +01:00
Christophe Siraut
02c524dd79 doc: chill:main:address-ref-from-best-addresse requires a language code 2025-02-07 10:53:53 +01:00
Christophe Siraut
bc7f0907ab doc: add languages population command 2025-02-03 14:05:08 +01:00
Christophe Siraut
fbdc0d32f0 ChillPersonBundle: Add numberOfDependents and numberOfDependentsWithDisabilities 2025-01-31 10:13:47 +01:00
Christophe Siraut
5f31473c90 ChillMainBundle: optionnaly mask aggegators in ExportType 2025-01-31 09:37:46 +01:00
Christophe Siraut
98cf167040 ChillPersonBundle: add aggregators for administrative status and employment status 2025-01-31 09:37:46 +01:00
Christophe Siraut
6c37d798bf ChillPersonBundle: add administrativeStatus property to Person 2025-01-31 09:06:45 +01:00
141 changed files with 3026 additions and 9659 deletions

View File

@@ -0,0 +1,6 @@
kind: DX
body: Create an unique source of trust for translations
time: 2025-01-31T13:18:01.239211506+01:00
custom:
Issue: "333"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Suggest all referrers within actions of the accompanying period when creating an activity
time: 2025-01-30T12:02:07.053587034+01:00
custom:
Issue: "349"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Add possibility to export a csv with all social issues and social actions
time: 2025-01-30T12:04:59.754998541+01:00
custom:
Issue: "343"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Restore document to previous kept version when a workflow is canceled
time: 2025-02-14T15:03:28.707250207+01:00
custom:
Issue: "360"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Add a list of third parties from within the admin (csv download)
time: 2025-02-19T12:09:28.487991703+01:00
custom:
Issue: "341"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: fix generation of document with accompanying period context, and list of activities and works
time: 2025-02-14T12:10:10.920355454+01:00
custom:
Issue: ""
SchemaChange: No schema change

3
.changes/v3.8.2.md Normal file
View File

@@ -0,0 +1,3 @@
## v3.8.2 - 2025-02-10
### Fixed
* ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ docker/rabbitmq/data
# in this development bundle, we want to ignore directories related to a real app # in this development bundle, we want to ignore directories related to a real app
assets/* assets/*
!assets/translator.ts
migrations/* migrations/*
templates/* templates/*
translations/* translations/*

View File

@@ -113,7 +113,7 @@ lint:
- export PATH="./node_modules/.bin:$PATH" - export PATH="./node_modules/.bin:$PATH"
script: script:
- yarn install --ignore-optional - yarn install --ignore-optional
- npx eslint-baseline "**/*.{js,vue}" - npx eslint-baseline "src/**/*.{js,ts,vue}"
cache: cache:
paths: paths:
- node_modules/ - node_modules/

View File

@@ -6,6 +6,10 @@ 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).
## v3.8.2 - 2025-02-10
### Fixed
* ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal
## v3.8.1 - 2025-02-05 ## v3.8.1 - 2025-02-05
### Fixed ### Fixed
* Fix household link in the parcours banner * Fix household link in the parcours banner

9
assets/translator.ts Normal file
View File

@@ -0,0 +1,9 @@
// @ts-ignore Cannot find module (when used within an app)
import { trans, getLocale, setLocale, setLocaleFallbacks } from "@symfony/ux-translator";
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
setLocale('fr');
export { trans };
// @ts-ignore Cannot find module (when used within an app)
export * from '../var/translations';

View File

@@ -75,6 +75,7 @@
"symfony/templating": "^5.4", "symfony/templating": "^5.4",
"symfony/translation": "^5.4", "symfony/translation": "^5.4",
"symfony/twig-bundle": "^5.4", "symfony/twig-bundle": "^5.4",
"symfony/ux-translator": "^2.22",
"symfony/validator": "^5.4", "symfony/validator": "^5.4",
"symfony/webpack-encore-bundle": "^1.11", "symfony/webpack-encore-bundle": "^1.11",
"symfony/workflow": "^5.4", "symfony/workflow": "^5.4",

View File

@@ -36,4 +36,5 @@ return [
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true], Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true], Chill\WopiBundle\ChillWopiBundle::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],
]; ];

View File

@@ -0,0 +1,3 @@
ux_translator:
# The directory where the JavaScript translations are dumped
dump_directory: '%kernel.project_dir%/var/translations'

View File

@@ -29,8 +29,7 @@ We strongly encourage you to initialize a git repository at this step, to track
# add the flex endpoints required for custom recipes # add the flex endpoints required for custom recipes
cat <<< "$(jq '.extra.symfony += {"endpoint": ["flex://defaults", "https://gitlab.com/api/v4/projects/57371968/repository/files/index.json/raw?ref=main"]}' composer.json)" > composer.json cat <<< "$(jq '.extra.symfony += {"endpoint": ["flex://defaults", "https://gitlab.com/api/v4/projects/57371968/repository/files/index.json/raw?ref=main"]}' composer.json)" > composer.json
# install chill and some dependencies # install chill and some dependencies
# TODO fix the suffix "alpha1" and replace by ^3.0.0 when version 3.0.0 will be released symfony composer require chill-project/chill-bundles ^3.7.1 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev symfony/amqp-messenger
symfony composer require chill-project/chill-bundles v3.0.0-RC3 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev
We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database. We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database.
You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually). You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually).
@@ -48,7 +47,7 @@ You must also accept to configure recipes from the contrib repository, unless yo
If you encounter this error during assets compilation (:code:`yarn run encore production`) (repeated multiple times): If you encounter this error during assets compilation (:code:`yarn run encore production`) (repeated multiple times):
.. code-block:: txt .. code-block::
[tsl] ERROR in /tmp/chill/v1/public/bundles/chillcalendar/types.ts(2,65) [tsl] ERROR in /tmp/chill/v1/public/bundles/chillcalendar/types.ts(2,65)
TS2307: Cannot find module '../../../ChillMainBundle/Resources/public/types' or its corresponding type declarations. TS2307: Cannot find module '../../../ChillMainBundle/Resources/public/types' or its corresponding type declarations.
@@ -74,14 +73,22 @@ or in the :code:`.env.local` file, which should not be committed to the git repo
You do not need to set variables for the smtp server, redis server and relatorio server, as they are generated automatically You do not need to set variables for the smtp server, redis server and relatorio server, as they are generated automatically
by the symfony server, from the docker compose services. by the symfony server, from the docker compose services.
The only required variable is the :code:`ADMIN_PASSWORD`. You can generate a hashed and salted admin password using the command The required variables are:
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`. Then,
- the :code:`ADMIN_PASSWORD`;
- the :code:`OVHCLOUD_DSN` variable;
:code:`ADMIN_PASSWORD`
^^^^^^^^^^^^^^^^^^^^^^
You can generate a hashed and salted admin password using the command
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`.Then,
you can either: you can either:
- add this password to the :code:`.env.local` file, you must escape the character :code:`$`: if the generated password - add this password to the :code:`.env.local` file, you must escape the character :code:`$`: if the generated password
is :code:`$2y$13$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm`, your :code:`.env.local` file will be: is :code:`$2y$13$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm`, your :code:`.env.local` file will be:
.. code-block:: env .. code-block:: bash
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
# note: if you copy-paste the line above, the password will be "admin". # note: if you copy-paste the line above, the password will be "admin".
@@ -89,12 +96,24 @@ you can either:
- add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env, - add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env,
not the password in clear text). not the password in clear text).
- set up the jwt authentication bundle :code:`OVHCLOUD_DSN` and sending SMS messages
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is a temporary dependency, for ensuring compatibility for previous behaviour.
You can set it to :code:`null://null` if you do not plan to use sending SMS.
.. code-block:: bash
OVHCLOUD_DSN=null://null
If you plan to do it, you can configure the notifier component `as described in the symfony documentation <https://symfony.com/doc/current/notifier.html#notifier-sms-channel>`_.
Some environment variables are available for the JWT authentication bundle in the :code:`.env` file. Some environment variables are available for the JWT authentication bundle in the :code:`.env` file.
Prepare migrations and other tools Prepare database, messenger queue, and other configuration
---------------------------------- ----------------------------------------------------------
To continue the installation process, you will have to run migrations: To continue the installation process, you will have to run migrations:
@@ -109,17 +128,22 @@ To continue the installation process, you will have to run migrations:
symfony console messenger:setup-transports symfony console messenger:setup-transports
# prepare some views # prepare some views
symfony console chill:db:sync-views symfony console chill:db:sync-views
# load languages data
symfony console chill:main:languages:populate
# generate jwt token, required for some api features (webdav access, ...) # generate jwt token, required for some api features (webdav access, ...)
symfony console lexik:jwt:generate-keypair symfony console lexik:jwt:generate-keypair
.. warning:: .. note::
If you encounter an error while running :code:`symfony console messenger:setup-transports`, you can set up the messenger If you encounter this error:
transport to redis, by adding this in the :code:`.env.local` or :code:`.env` file:
.. code-block::
No transport supports the given Messenger DSN.
Please check that you installed the package `symfony/amqp-messenger`.
.. code-block:: env
MESSENGER_TRANSPORT_DSN=redis://${REDIS_HOST}:${REDIS_PORT}/messages
Start your web server locally Start your web server locally
----------------------------- -----------------------------

View File

@@ -41,16 +41,18 @@ Postal code are loaded from this database. There is no need to load postal codes
The data are prepared for Chill (`See this repository <https://gitea.champs-libres.be/Chill-project/belgian-bestaddresses-transform/releases>`_). The data are prepared for Chill (`See this repository <https://gitea.champs-libres.be/Chill-project/belgian-bestaddresses-transform/releases>`_).
One can select postal code by his first number (:code:`1xxx` for postal codes from 1000 to 1999), or a limited list for development purpose. One can select postal code by his first number (:code:`1xxx` for postal codes from 1000 to 1999), or a limited list for development purpose.
The command expects a language code as first argument.
.. code-block:: bash .. code-block:: bash
# load postal code from 1000 to 3999: # load postal code from 1000 to 3999:
bin/console chill:main:address-ref-from-best-addresse 1xxx 2xxx 3xxx bin/console chill:main:address-ref-from-best-addresse fr 1xxx 2xxx 3xxx
# load only an extract (for dev purposes) # load only an extract (for dev purposes)
bin/console chill:main:address-ref-from-best-addresse extract bin/console chill:main:address-ref-from-best-addresse fr extract
# load full addresses (discouraged) # load full addresses (discouraged)
bin/console chill:main:address-ref-from-best-addresse full bin/console chill:main:address-ref-from-best-addresse fr full
.. note:: .. note::

View File

@@ -6,15 +6,12 @@
"@apidevtools/swagger-cli": "^4.0.4", "@apidevtools/swagger-cli": "^4.0.4",
"@babel/core": "^7.20.5", "@babel/core": "^7.20.5",
"@babel/preset-env": "^7.20.2", "@babel/preset-env": "^7.20.2",
"@ckeditor/ckeditor5-build-classic": "^41.4.2", "@ckeditor/ckeditor5-vue": "^7.3.0",
"@ckeditor/ckeditor5-dev-translations": "^40.2.0",
"@ckeditor/ckeditor5-dev-utils": "^40.2.0",
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
"@ckeditor/ckeditor5-vue": "^5.1.0",
"@eslint/js": "^9.14.0", "@eslint/js": "^9.14.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/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",
@@ -23,12 +20,14 @@
"bindings": "^1.5.0", "bindings": "^1.5.0",
"bootstrap": "5.2.3", "bootstrap": "5.2.3",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"ckeditor5": "^44.1.0",
"dompurify": "^3.1.0", "dompurify": "^3.1.0",
"eslint": "^9.14.0", "eslint": "^9.14.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.30.0", "eslint-plugin-vue": "^9.30.0",
"fork-awesome": "^1.1.7", "fork-awesome": "^1.1.7",
"intl-messageformat": "^10.5.11",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"node-sass": "^8.0.0", "node-sass": "^8.0.0",
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
@@ -54,6 +53,7 @@
"@fullcalendar/timegrid": "^6.1.4", "@fullcalendar/timegrid": "^6.1.4",
"@fullcalendar/vue3": "^6.1.4", "@fullcalendar/vue3": "^6.1.4",
"@popperjs/core": "^2.9.2", "@popperjs/core": "^2.9.2",
"@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/leaflet": "^1.9.3", "@types/leaflet": "^1.9.3",
"bootstrap-icons": "^1.11.3", "bootstrap-icons": "^1.11.3",
@@ -72,7 +72,7 @@
"vuex": "^4.0.0" "vuex": "^4.0.0"
}, },
"browserslist": [ "browserslist": [
"Firefox ESR" "defaults and fully supports es6-module and not dead"
], ],
"scripts": { "scripts": {
"dev-server": "encore dev-server", "dev-server": "encore dev-server",

View File

@@ -30,13 +30,14 @@
<ul class="record_actions"> <ul class="record_actions">
<li class="add-persons"> <li class="add-persons">
<add-persons <add-persons
button-title="activity.add_persons" :buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
modal-title="activity.add_persons" :modalTitle="trans(ACTIVITY_ADD_PERSONS)"
:key="addPersons.key" v-bind:key="addPersons.key"
:options="addPersonsOptions" v-bind:options="addPersonsOptions"
@add-new-persons="addNewPersons" @addNewPersons="addNewPersons"
ref="addPersons" ref="addPersons"
/> >
</add-persons>
</li> </li>
</ul> </ul>
</teleport> </teleport>
@@ -47,6 +48,14 @@ import { mapState, mapGetters } from "vuex";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue"; import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue"; import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue"; import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import {
ACTIVITY_BLOC_PERSONS,
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
ACTIVITY_BLOC_THIRDPARTY,
ACTIVITY_BLOC_USERS,
ACTIVITY_ADD_PERSONS,
trans,
} from "translator";
export default { export default {
name: "ConcernedGroups", name: "ConcernedGroups",
@@ -55,18 +64,24 @@ export default {
PersonsBloc, PersonsBloc,
PersonText, PersonText,
}, },
setup() {
return {
trans,
ACTIVITY_ADD_PERSONS,
};
},
data() { data() {
return { return {
personsBlocs: [ personsBlocs: [
{ {
key: "persons", key: "persons",
title: "activity.bloc_persons", title: trans(ACTIVITY_BLOC_PERSONS),
persons: [], persons: [],
included: false, included: false,
}, },
{ {
key: "personsAssociated", key: "personsAssociated",
title: "activity.bloc_persons_associated", title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
persons: [], persons: [],
included: window.activity included: window.activity
? window.activity.activityType.personsVisible !== 0 ? window.activity.activityType.personsVisible !== 0
@@ -82,7 +97,7 @@ export default {
}, },
{ {
key: "thirdparty", key: "thirdparty",
title: "activity.bloc_thirdparty", title: trans(ACTIVITY_BLOC_THIRDPARTY),
persons: [], persons: [],
included: window.activity included: window.activity
? window.activity.activityType.thirdPartiesVisible !== 0 ? window.activity.activityType.thirdPartiesVisible !== 0
@@ -90,7 +105,7 @@ export default {
}, },
{ {
key: "users", key: "users",
title: "activity.bloc_users", title: trans(ACTIVITY_BLOC_USERS),
persons: [], persons: [],
included: window.activity included: window.activity
? window.activity.activityType.usersVisible !== 0 ? window.activity.activityType.usersVisible !== 0

View File

@@ -2,7 +2,7 @@
<teleport to="#location"> <teleport to="#location">
<div class="mb-3 row"> <div class="mb-3 row">
<label :class="locationClassList"> <label :class="locationClassList">
{{ $t("activity.location") }} {{ trans(ACTIVITY_LOCATION) }}
</label> </label>
<div class="col-sm-8"> <div class="col-sm-8">
<VueMultiselect <VueMultiselect
@@ -13,17 +13,17 @@
open-direction="top" open-direction="top"
:multiple="false" :multiple="false"
:searchable="true" :searchable="true"
:placeholder="$t('activity.choose_location')" :placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
:custom-label="customLabel" :custom-label="customLabel"
:select-label="$t('multiselect.select_label')" :select-label="trans(MULTISELECT_SELECT_LABEL)"
:deselect-label="$t('multiselect.deselect_label')" :deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
:selected-label="$t('multiselect.selected_label')" :selected-label="trans(MULTISELECT_SELECTED_LABEL)"
:options="availableLocations" :options="availableLocations"
group-values="locations" group-values="locations"
group-label="locationGroup" group-label="locationGroup"
v-model="location" v-model="location"
/> />
<new-location :available-locations="availableLocations" /> <new-location v-bind:available-locations="availableLocations" />
</div> </div>
</div> </div>
</teleport> </teleport>
@@ -33,6 +33,14 @@
import { mapState, mapGetters } from "vuex"; import { mapState, mapGetters } from "vuex";
import VueMultiselect from "vue-multiselect"; import VueMultiselect from "vue-multiselect";
import NewLocation from "./Location/NewLocation.vue"; import NewLocation from "./Location/NewLocation.vue";
import {
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
} from "translator";
export default { export default {
name: "Location", name: "Location",
@@ -40,6 +48,16 @@ export default {
NewLocation, NewLocation,
VueMultiselect, VueMultiselect,
}, },
setup() {
return {
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
};
},
data() { data() {
return { return {
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`, locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,

View File

@@ -3,7 +3,7 @@
<ul class="record_actions"> <ul class="record_actions">
<li> <li>
<a class="btn btn-sm btn-create" @click="openModal"> <a class="btn btn-sm btn-create" @click="openModal">
{{ $t("activity.create_new_location") }} {{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</a> </a>
</li> </li>
</ul> </ul>
@@ -11,12 +11,12 @@
<teleport to="body"> <teleport to="body">
<modal <modal
v-if="modal.showModal" v-if="modal.showModal"
:modal-dialog-class="modal.modalDialogClass" :modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false" @close="modal.showModal = false"
> >
<template #header> <template #header>
<h3 class="modal-title"> <h3 class="modal-title">
{{ $t("activity.create_new_location") }} {{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</h3> </h3>
</template> </template>
<template #body> <template #body>
@@ -37,7 +37,7 @@
v-model="selectType" v-model="selectType"
> >
<option selected disabled value=""> <option selected disabled value="">
{{ $t("activity.choose_location_type") }} {{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
</option> </option>
<option <option
v-for="t in locationTypes" v-for="t in locationTypes"
@@ -48,7 +48,7 @@
</option> </option>
</select> </select>
<label>{{ <label>{{
$t("activity.location_fields.type") trans(ACTIVITY_LOCATION_FIELDS_TYPE)
}}</label> }}</label>
</div> </div>
@@ -60,14 +60,14 @@
placeholder placeholder
/> />
<label for="name">{{ <label for="name">{{
$t("activity.location_fields.name") trans(ACTIVITY_LOCATION_FIELDS_NAME)
}}</label> }}</label>
</div> </div>
<add-address <add-address
:context="addAddress.context" :context="addAddress.context"
:options="addAddress.options" :options="addAddress.options"
:address-changed-callback="submitNewAddress" :addressChangedCallback="submitNewAddress"
v-if="showAddAddress" v-if="showAddAddress"
ref="addAddress" ref="addAddress"
/> />
@@ -80,7 +80,7 @@
placeholder placeholder
/> />
<label for="phonenumber1">{{ <label for="phonenumber1">{{
$t("activity.location_fields.phonenumber1") trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
}}</label> }}</label>
</div> </div>
<div class="form-floating mb-3" v-if="hasPhonenumber1"> <div class="form-floating mb-3" v-if="hasPhonenumber1">
@@ -91,7 +91,7 @@
placeholder placeholder
/> />
<label for="phonenumber2">{{ <label for="phonenumber2">{{
$t("activity.location_fields.phonenumber2") trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
}}</label> }}</label>
</div> </div>
<div class="form-floating mb-3" v-if="showContactData"> <div class="form-floating mb-3" v-if="showContactData">
@@ -102,7 +102,7 @@
placeholder placeholder
/> />
<label for="email">{{ <label for="email">{{
$t("activity.location_fields.email") trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
}}</label> }}</label>
</div> </div>
</form> </form>
@@ -112,7 +112,7 @@
class="btn btn-save" class="btn btn-save"
@click.prevent="saveNewLocation" @click.prevent="saveNewLocation"
> >
{{ $t("action.save") }} {{ trans(SAVE) }}
</button> </button>
</template> </template>
</modal> </modal>
@@ -126,6 +126,17 @@ import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"
import { mapState } from "vuex"; import { mapState } from "vuex";
import { getLocationTypes } from "../../api"; import { getLocationTypes } from "../../api";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import {
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
trans,
} from "translator";
export default { export default {
name: "NewLocation", name: "NewLocation",
@@ -133,6 +144,19 @@ export default {
Modal, Modal,
AddAddress, AddAddress,
}, },
setup() {
return {
trans,
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
};
},
props: ["availableLocations"], props: ["availableLocations"],
data() { data() {
return { return {

View File

@@ -3,7 +3,7 @@
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-4"> <div class="col-4">
<label :class="socialIssuesClassList">{{ <label :class="socialIssuesClassList">{{
$t("activity.social_issues") trans(ACTIVITY_SOCIAL_ISSUES)
}}</label> }}</label>
</div> </div>
<div class="col-8"> <div class="col-8">
@@ -12,8 +12,9 @@
:key="issue.id" :key="issue.id"
:issue="issue" :issue="issue"
:selection="socialIssuesSelected" :selection="socialIssuesSelected"
@update-selected="updateIssuesSelected" @updateSelected="updateIssuesSelected"
/> >
</check-social-issue>
<div class="my-3"> <div class="my-3">
<VueMultiselect <VueMultiselect
@@ -31,10 +32,11 @@
:allow-empty="true" :allow-empty="true"
:show-labels="false" :show-labels="false"
:loading="issueIsLoading" :loading="issueIsLoading"
:placeholder="$t('activity.choose_other_social_issue')" :placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
:options="socialIssuesOther" :options="socialIssuesOther"
@select="addIssueInList" @select="addIssueInList"
/> >
</VueMultiselect>
</div> </div>
</div> </div>
</div> </div>
@@ -42,26 +44,28 @@
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-4"> <div class="col-4">
<label :class="socialActionsClassList">{{ <label :class="socialActionsClassList">{{
$t("activity.social_actions") trans(ACTIVITY_SOCIAL_ACTIONS)
}}</label> }}</label>
</div> </div>
<div class="col-8"> <div class="col-8">
<div v-if="actionIsLoading === true"> <div v-if="actionIsLoading === true">
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg" /> <i
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
></i>
</div> </div>
<span <span
v-else-if="socialIssuesSelected.length === 0" v-else-if="socialIssuesSelected.length === 0"
class="inline-choice chill-no-data-statement mt-3" class="inline-choice chill-no-data-statement mt-3"
> >
{{ $t("activity.select_first_a_social_issue") }} {{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
</span> </span>
<template v-else-if="socialActionsList.length > 0"> <template
<div v-else-if="
v-if=" socialActionsList.length > 0 &&
socialIssuesSelected.length || (socialIssuesSelected.length ||
socialActionsSelected.length socialActionsSelected.length)
" "
> >
<check-social-action <check-social-action
@@ -69,9 +73,9 @@
:key="action.id" :key="action.id"
:action="action" :action="action"
:selection="socialActionsSelected" :selection="socialActionsSelected"
@update-selected="updateActionsSelected" @updateSelected="updateActionsSelected"
/> >
</div> </check-social-action>
</template> </template>
<span <span
@@ -80,7 +84,7 @@
" "
class="inline-choice chill-no-data-statement mt-3" class="inline-choice chill-no-data-statement mt-3"
> >
{{ $t("activity.social_action_list_empty") }} {{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
</span> </span>
</div> </div>
</div> </div>
@@ -92,6 +96,14 @@ import VueMultiselect from "vue-multiselect";
import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue"; import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue"; import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
import { getSocialIssues, getSocialActionByIssue } from "../api.js"; import { getSocialIssues, getSocialActionByIssue } from "../api.js";
import {
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
trans,
} from "translator";
export default { export default {
name: "SocialIssuesAcc", name: "SocialIssuesAcc",
@@ -100,6 +112,16 @@ export default {
CheckSocialAction, CheckSocialAction,
VueMultiselect, VueMultiselect,
}, },
setup() {
return {
trans,
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
};
},
data() { data() {
return { return {
issueIsLoading: false, issueIsLoading: false,
@@ -133,7 +155,7 @@ export default {
this.actionAreLoaded = false; this.actionAreLoaded = false;
getSocialIssues().then( getSocialIssues().then(
(response) => (response) =>
new Promise((resolve, reject) => { new Promise((resolve) => {
this.$store.commit("updateIssuesOther", response.results); this.$store.commit("updateIssuesOther", response.results);
/* Add in list the issues already associated (if not yet listed) /* Add in list the issues already associated (if not yet listed)
@@ -208,7 +230,7 @@ export default {
this.actionIsLoading = true; this.actionIsLoading = true;
getSocialActionByIssue(item.id).then( getSocialActionByIssue(item.id).then(
(actions) => (actions) =>
new Promise((resolve, reject) => { new Promise((resolve) => {
actions.results.forEach((action) => { actions.results.forEach((action) => {
this.$store.commit("addActionInList", action); this.$store.commit("addActionInList", action);
}, this); }, this);
@@ -235,7 +257,6 @@ export default {
}; };
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
<style lang="scss" scoped> <style lang="scss" scoped>
span.multiselect__single { span.multiselect__single {
display: none !important; display: none !important;

View File

@@ -26,6 +26,7 @@ const store = createStore({
state: { state: {
me: null, me: null,
activity: window.activity, activity: window.activity,
accompanyingPeriodWorks: [],
socialIssuesOther: [], socialIssuesOther: [],
socialActionsList: [], socialActionsList: [],
availableLocations: [], availableLocations: [],
@@ -41,7 +42,7 @@ const store = createStore({
const allEntities = [ const allEntities = [
...store.getters.suggestedPersons, ...store.getters.suggestedPersons,
...store.getters.suggestedRequestor, ...store.getters.suggestedRequestor,
...store.getters.suggestedUser, ...store.getters.suggestedUsers,
...store.getters.suggestedResources, ...store.getters.suggestedResources,
]; ];
const uniqueIds = [ const uniqueIds = [
@@ -80,8 +81,7 @@ const store = createStore({
state.activity.activityType.thirdPartiesVisible !== 0), state.activity.activityType.thirdPartiesVisible !== 0),
); );
}, },
suggestedUser(state) { suggestedUsers(state) {
// console.log('current user', state.me)
const existingUserIds = state.activity.users.map((p) => p.id); const existingUserIds = state.activity.users.map((p) => p.id);
let suggestedUsers = let suggestedUsers =
state.activity.activityType.usersVisible === 0 state.activity.activityType.usersVisible === 0
@@ -90,11 +90,18 @@ const store = createStore({
(u) => u !== null && !existingUserIds.includes(u.id), (u) => u !== null && !existingUserIds.includes(u.id),
); );
state.accompanyingPeriodWorks.forEach((work) => {
work.referrers.forEach((r) => {
if (!existingUserIds.includes(r.id)) {
suggestedUsers.push(r);
}
});
});
// Add the current user from the state // Add the current user from the state
if (state.me && !existingUserIds.includes(state.me.id)) { if (state.me && !existingUserIds.includes(state.me.id)) {
suggestedUsers.push(state.me); suggestedUsers.push(state.me);
} }
console.log("suggested users", suggestedUsers); // console.log("suggested users", suggestedUsers);
return suggestedUsers; return suggestedUsers;
}, },
@@ -223,6 +230,9 @@ const store = createStore({
addAvailableLocationGroup(state, group) { addAvailableLocationGroup(state, group) {
state.availableLocations.push(group); state.availableLocations.push(group);
}, },
setAccompanyingPeriodWorks(state, works) {
state.accompanyingPeriodWorks = works;
},
}, },
actions: { actions: {
addIssueSelected({ commit }, issue) { addIssueSelected({ commit }, issue) {
@@ -341,6 +351,17 @@ const store = createStore({
} }
commit("updateLocation", value); commit("updateLocation", value);
}, },
async fetchAccompanyingPeriodWorks({ state, commit }) {
const accompanyingPeriodId = state.activity.accompanyingPeriod.id;
const url = `/api/1.0/person/accompanying-course/${accompanyingPeriodId}/works.json`;
try {
const works = await makeFetch("GET", url);
// console.log("works", works);
commit("setAccompanyingPeriodWorks", works);
} catch (error) {
console.error("Failed to fetch accompanying period works:", error);
}
},
getWhoAmI({ commit }) { getWhoAmI({ commit }) {
const url = `/api/1.0/main/whoami.json`; const url = `/api/1.0/main/whoami.json`;
makeFetch("GET", url).then((user) => { makeFetch("GET", url).then((user) => {
@@ -353,5 +374,6 @@ const store = createStore({
store.dispatch("getWhoAmI"); store.dispatch("getWhoAmI");
prepareLocations(store); prepareLocations(store);
store.dispatch("fetchAccompanyingPeriodWorks");
export default store; export default store;

View File

@@ -143,7 +143,10 @@ class ListActivitiesByAccompanyingPeriodContext implements
array_filter( array_filter(
$works, $works,
function ($work) use ($user) { function ($work) use ($user) {
$workUsernames = array_map(static fn (User $user) => $user['username'], $work['referrers'] ?? []); $workUsernames = [];
foreach ($work['referrers'] as $referrer) {
$workUsernames[] = $referrer['username'];
}
return \in_array($user->getUserIdentifier(), $workUsernames, true); return \in_array($user->getUserIdentifier(), $workUsernames, true);
} }

View File

@@ -102,6 +102,32 @@ activity:
Remove a document: Supprimer le document Remove a document: Supprimer le document
comment: Commentaire comment: Commentaire
deleted: Échange supprimé deleted: Échange supprimé
errors: Le formulaire contient des erreurs
social_issues: Problématiques sociales
choose_other_social_issue: Ajouter une autre problématique sociale...
social_actions: Actions d'accompagnement
select_first_a_social_issue: Sélectionnez d'abord une problématique sociale
social_action_list_empty: Aucune action sociale disponible
add_persons: Ajouter des personnes concernées
bloc_persons: Usagers
bloc_persons_associated: Usagers du parcours
bloc_persons_not_associated: Tiers non-pro.
bloc_thirdparty: Tiers professionnels
bloc_users: T(M)S
location: Localisation
choose_location: Choisissez une localisation
choose_location_type: Choisissez un type de localisation
create_new_location: Créer une nouvelle localisation
location_fields:
name: Nom
type: Type
phonenumber1: Téléphone
phonenumber2: Autre téléphone
email: Adresse courriel
create_address: Créer une adresse
edit_address: Modifier l'adresse
No documents: Aucun document No documents: Aucun document
# activity filter in list page # activity filter in list page

View File

@@ -15,6 +15,7 @@ use Chill\CalendarBundle\Repository\CalendarRepository;
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;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -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, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
#[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

View File

@@ -15,6 +15,7 @@ use Chill\CalendarBundle\Repository\CalendarRangeRepository;
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;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
class CalendarRangeAPIController extends ApiController class CalendarRangeAPIController extends ApiController
{ {
public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository) {} public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
#[Route(path: '/api/1.0/calendar/calendar-range-available/{id}.{_format}', name: 'chill_api_single_calendar_range_available', requirements: ['_format' => 'json'])] #[Route(path: '/api/1.0/calendar/calendar-range-available/{id}.{_format}', name: 'chill_api_single_calendar_range_available', requirements: ['_format' => 'json'])]
public function availableRanges(User $user, Request $request, string $_format): JsonResponse public function availableRanges(User $user, Request $request, string $_format): JsonResponse

View File

@@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
@@ -27,7 +28,10 @@ class StoredObjectApiController extends ApiController
private readonly Security $security, private readonly Security $security,
private readonly SerializerInterface $serializer, private readonly SerializerInterface $serializer,
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
) {} ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
}
/** /**
* Creates a new stored object. * Creates a new stored object.

View File

@@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Tests\Controller;
use Chill\DocStoreBundle\Controller\StoredObjectApiController; use Chill\DocStoreBundle\Controller\StoredObjectApiController;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@@ -45,7 +46,9 @@ class StoredObjectApiControllerTest extends TestCase
{"type": "stored-object", "id": 1} {"type": "stored-object", "id": 1}
JSON); JSON);
$controller = new StoredObjectApiController($security, $serializer, $entityManager); $managerRegistry = $this->createMock(ManagerRegistry::class);
$controller = new StoredObjectApiController($security, $serializer, $entityManager, $managerRegistry);
$actual = $controller->createStoredObject(); $actual = $controller->createStoredObject();

View File

@@ -1,9 +1,5 @@
#events #events
Name: Nom
Label: Nom
Date: Date
Event type : Type d'événement Event type : Type d'événement
See: Voir
Event: Événement Event: Événement
Events: Événements Events: Événements
'Event : %label%': Événement "%label%" 'Event : %label%': Événement "%label%"
@@ -123,7 +119,6 @@ Role: Rôles
Role creation: Nouveau rôle Role creation: Nouveau rôle
Role edit: Modifier un rôle Role edit: Modifier un rôle
'': ''
xlsx: xlsx xlsx: xlsx
ods: ods ods: ods
csv: csv csv: csv

View File

@@ -39,6 +39,8 @@ abstract class AbstractCRUDController extends AbstractController
*/ */
protected array $crudConfig = []; protected array $crudConfig = [];
public function __construct(protected ManagerRegistry $managerRegistry) {}
/** /**
* get the role given from the config. * get the role given from the config.
*/ */
@@ -213,7 +215,7 @@ abstract class AbstractCRUDController extends AbstractController
protected function getManagerRegistry(): ManagerRegistry protected function getManagerRegistry(): ManagerRegistry
{ {
return $this->container->get(ManagerRegistry::class); return $this->managerRegistry;
} }
/** /**

View File

@@ -13,6 +13,7 @@ namespace Chill\MainBundle\CRUD\Controller;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -23,6 +24,11 @@ use Symfony\Component\Validator\ConstraintViolationListInterface;
class ApiController extends AbstractCRUDController class ApiController extends AbstractCRUDController
{ {
public function __construct(ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
/** /**
* Base method for handling api action. * Base method for handling api action.
* *

View File

@@ -13,6 +13,7 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
@@ -20,7 +21,10 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
class AddressApiController extends ApiController class AddressApiController extends ApiController
{ {
public function __construct(private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry) {} public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
/** /**
* Duplicate an existing address. * Duplicate an existing address.

View File

@@ -17,6 +17,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Repository\AddressReferenceRepository; use Chill\MainBundle\Repository\AddressReferenceRepository;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -26,7 +27,10 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
final class AddressReferenceAPIController extends ApiController final class AddressReferenceAPIController extends ApiController
{ {
public function __construct(private readonly AddressReferenceRepository $addressReferenceRepository, private readonly PaginatorFactory $paginatorFactory) {} public function __construct(private readonly AddressReferenceRepository $addressReferenceRepository, private readonly PaginatorFactory $paginatorFactory, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
#[Route(path: '/api/1.0/main/address-reference/by-postal-code/{id}/search.json')] #[Route(path: '/api/1.0/main/address-reference/by-postal-code/{id}/search.json')]
public function search(PostalCode $postalCode, Request $request): JsonResponse public function search(PostalCode $postalCode, Request $request): JsonResponse

View File

@@ -13,10 +13,16 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class CivilityApiController extends ApiController class CivilityApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format) protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{ {
return $query->addOrderBy('e.order', 'ASC'); return $query->addOrderBy('e.order', 'ASC');

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 Doctrine\Persistence\ManagerRegistry;
class CountryApiController extends ApiController {} class CountryApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}

View File

@@ -13,10 +13,16 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class GenderApiController extends ApiController class GenderApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void protected function customizeQuery(string $action, Request $request, $query): void
{ {
$query $query

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 Doctrine\Persistence\ManagerRegistry;
class GeographicalUnitApiController extends ApiController {} class GeographicalUnitApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}

View File

@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
@@ -21,6 +22,11 @@ use Symfony\Component\HttpFoundation\Request;
*/ */
class LocationApiController extends ApiController class LocationApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void protected function customizeQuery(string $action, Request $request, $query): void
{ {
$query $query

View File

@@ -12,6 +12,7 @@ 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 Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
@@ -19,6 +20,11 @@ use Symfony\Component\HttpFoundation\Request;
*/ */
class LocationTypeApiController extends ApiController class LocationTypeApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function customizeQuery(string $action, Request $request, $query): void public function customizeQuery(string $action, Request $request, $query): void
{ {
$query->andWhere( $query->andWhere(

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\PostalCodeRepositoryInterface; use Chill\MainBundle\Repository\PostalCodeRepositoryInterface;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -26,7 +27,10 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
final class PostalCodeAPIController extends ApiController final class PostalCodeAPIController extends ApiController
{ {
public function __construct(private readonly CountryRepository $countryRepository, private readonly PostalCodeRepositoryInterface $postalCodeRepository, private readonly PaginatorFactory $paginatorFactory) {} public function __construct(private readonly CountryRepository $countryRepository, private readonly PostalCodeRepositoryInterface $postalCodeRepository, private readonly PaginatorFactory $paginatorFactory, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
#[Route(path: '/api/1.0/main/postal-code/search.json')] #[Route(path: '/api/1.0/main/postal-code/search.json')]
public function search(Request $request): JsonResponse public function search(Request $request): JsonResponse

View File

@@ -12,10 +12,16 @@ 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 Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class ScopeApiController extends ApiController class ScopeApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void protected function customizeQuery(string $action, Request $request, $query): void
{ {
if ('_index' === $action) { if ('_index' === $action) {

View File

@@ -15,6 +15,7 @@ use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Security\ChillSecurity; use Chill\MainBundle\Security\ChillSecurity;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -22,7 +23,10 @@ use Symfony\Component\Routing\Annotation\Route;
class UserApiController extends ApiController class UserApiController extends ApiController
{ {
public function __construct(private readonly ChillSecurity $security) {} public function __construct(private readonly ChillSecurity $security, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
#[Route(path: '/api/1.0/main/user-current-location.{_format}', name: 'chill_main_user_current_location', requirements: ['_format' => 'json'])] #[Route(path: '/api/1.0/main/user-current-location.{_format}', name: 'chill_main_user_current_location', requirements: ['_format' => 'json'])]
public function currentLocation(mixed $_format): JsonResponse public function currentLocation(mixed $_format): JsonResponse

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 Doctrine\Persistence\ManagerRegistry;
class UserGroupApiController extends ApiController {} class UserGroupApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}

View File

@@ -12,10 +12,16 @@ 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 Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class UserJobApiController extends ApiController class UserJobApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void protected function customizeQuery(string $action, Request $request, $query): void
{ {
if ('_index' === $action) { if ('_index' === $action) {

View File

@@ -18,6 +18,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class ExportType extends AbstractType class ExportType extends AbstractType
{ {
@@ -29,7 +30,15 @@ class ExportType extends AbstractType
final public const PICK_FORMATTER_KEY = 'pick_formatter'; final public const PICK_FORMATTER_KEY = 'pick_formatter';
public function __construct(private readonly ExportManager $exportManager, private readonly SortExportElement $sortExportElement) {} private array $personFieldsConfig;
public function __construct(
private readonly ExportManager $exportManager,
private readonly SortExportElement $sortExportElement,
protected ParameterBagInterface $parameterBag,
) {
$this->personFieldsConfig = $parameterBag->get('chill_person.person_fields');
}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
@@ -77,6 +86,17 @@ class ExportType extends AbstractType
); );
foreach ($aggregators as $alias => $aggregator) { foreach ($aggregators as $alias => $aggregator) {
/*
* eventually mask aggregator
*/
if (str_starts_with((string) $alias, 'person_') and str_ends_with((string) $alias, '_aggregator')) {
$field = preg_replace(['/person_/', '/_aggregator/'], '', (string) $alias);
if (array_key_exists($field, $this->personFieldsConfig) and 'visible' !== $this->personFieldsConfig[$field]) {
continue;
}
}
$aggregatorBuilder->add($alias, AggregatorType::class, [ $aggregatorBuilder->add($alias, AggregatorType::class, [
'aggregator_alias' => $alias, 'aggregator_alias' => $alias,
'export_manager' => $this->exportManager, 'export_manager' => $this->exportManager,

View File

@@ -1,30 +1,32 @@
import ClassicEditorBase from "@ckeditor/ckeditor5-editor-classic/src/classiceditor"; import {
import EssentialsPlugin from "@ckeditor/ckeditor5-essentials/src/essentials"; Essentials,
import MarkdownPlugin from "@ckeditor/ckeditor5-markdown-gfm/src/markdown"; Bold,
import BoldPlugin from "@ckeditor/ckeditor5-basic-styles/src/bold"; Italic,
import ItalicPlugin from "@ckeditor/ckeditor5-basic-styles/src/italic"; Paragraph,
import BlockQuotePlugin from "@ckeditor/ckeditor5-block-quote/src/blockquote"; Markdown,
import HeadingPlugin from "@ckeditor/ckeditor5-heading/src/heading"; BlockQuote,
import LinkPlugin from "@ckeditor/ckeditor5-link/src/link"; Heading,
import ListPlugin from "@ckeditor/ckeditor5-list/src/list"; Link,
import ParagraphPlugin from "@ckeditor/ckeditor5-paragraph/src/paragraph"; List,
} from "ckeditor5";
import coreTranslations from "ckeditor5/translations/fr.js";
import "ckeditor5/ckeditor5.css";
import "./index.scss"; import "./index.scss";
export default class ClassicEditor extends ClassicEditorBase {} export default {
plugins: [
ClassicEditor.builtinPlugins = [ Essentials,
EssentialsPlugin, Markdown,
MarkdownPlugin, Bold,
BoldPlugin, Italic,
ItalicPlugin, BlockQuote,
BlockQuotePlugin, Heading,
HeadingPlugin, Link,
LinkPlugin, List,
ListPlugin, Paragraph,
ParagraphPlugin, ],
];
ClassicEditor.defaultConfig = {
toolbar: { toolbar: {
items: [ items: [
"heading", "heading",
@@ -39,5 +41,6 @@ ClassicEditor.defaultConfig = {
"redo", "redo",
], ],
}, },
language: "fr", translations: [coreTranslations],
licenseKey: "GPL",
}; };

View File

@@ -1,14 +1,12 @@
import ClassicEditor from "./editor_config"; import config from "./editor_config";
import { ClassicEditor } from "ckeditor5";
const ckeditorFields: NodeListOf<HTMLTextAreaElement> = const ckeditorFields: NodeListOf<HTMLTextAreaElement> =
document.querySelectorAll("textarea[ckeditor]"); document.querySelectorAll("textarea[ckeditor]");
ckeditorFields.forEach((field: HTMLTextAreaElement): void => { ckeditorFields.forEach((field: HTMLTextAreaElement): void => {
ClassicEditor.create(field) ClassicEditor.create(field, config).catch((error) => {
.then((editor) => {
//console.log( 'CkEditor was initialized', editor );
})
.catch((error) => {
console.error(error.stack); console.error(error.stack);
throw error;
}); });
}); });
//Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea')); //Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea'));

View File

@@ -1,11 +1,10 @@
import { createApp } from "vue"; import { createApp } from "vue";
import NotificationReadToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadToggle.vue"; import NotificationReadToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadToggle.vue";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n"; import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import NotificationReadAllToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadAllToggle.vue";
const i18n = _createI18n({}); const i18n = _createI18n({});
window.addEventListener("DOMContentLoaded", function (e) { window.addEventListener("DOMContentLoaded", function () {
document document
.querySelectorAll(".notification_toggle_read_status") .querySelectorAll(".notification_toggle_read_status")
.forEach(function (el, i) { .forEach(function (el, i) {

View File

@@ -12,6 +12,10 @@ function loadDynamicPicker(element) {
let apps = element.querySelectorAll('[data-module="pick-dynamic"]'); let apps = element.querySelectorAll('[data-module="pick-dynamic"]');
apps.forEach(function (el) { apps.forEach(function (el) {
let suggested;
let as_id;
let submit_on_adding_new_entity;
let label;
const isMultiple = parseInt(el.dataset.multiple) === 1, const isMultiple = parseInt(el.dataset.multiple) === 1,
uniqId = el.dataset.uniqid, uniqId = el.dataset.uniqid,
input = element.querySelector( input = element.querySelector(
@@ -23,10 +27,10 @@ function loadDynamicPicker(element) {
: input.value === "[]" || input.value === "" : input.value === "[]" || input.value === ""
? null ? null
: [JSON.parse(input.value)]; : [JSON.parse(input.value)];
(suggested = JSON.parse(el.dataset.suggested)), suggested = JSON.parse(el.dataset.suggested);
(as_id = parseInt(el.dataset.asId) === 1), as_id = parseInt(el.dataset.asId) === 1;
(submit_on_adding_new_entity = submit_on_adding_new_entity =
parseInt(el.dataset.submitOnAddingNewEntity) === 1); parseInt(el.dataset.submitOnAddingNewEntity) === 1;
label = el.dataset.label; label = el.dataset.label;
if (!isMultiple) { if (!isMultiple) {
@@ -173,7 +177,7 @@ document.addEventListener("pick-entity-type-action", function (e) {
} }
}); });
document.addEventListener("DOMContentLoaded", function (e) { document.addEventListener("DOMContentLoaded", function () {
loadDynamicPicker(document); loadDynamicPicker(document);
}); });

View File

@@ -232,20 +232,9 @@ const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
</div> </div>
</div> </div>
</div> </div>
<div class="row my-2">
<button
type="submit"
class="btn btn-sm btn-misc"
>
<i class="fa fa-fw fa-filter"></i>Filtrer
</button>
</div> </div>
</div> </div>
</div> </div>
<div></div>
</div>
</form> </form>
</div> </div>

View File

@@ -70,7 +70,8 @@ const clickOnAddButton = () => {
<style scoped lang="scss"> <style scoped lang="scss">
.item-bloc { .item-bloc {
&.isPicked { &.isPicked {
background: linear-gradient( background:
linear-gradient(
180deg, 180deg,
rgba(25, 135, 84, 1) 0px, rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px rgba(25, 135, 84, 0) 9px

View File

@@ -1,61 +1,65 @@
<template> <template>
<span v-if="entity.type === 'person'" class="badge rounded-pill bg-person"> <span
{{ $t("person") }} v-if="props.entity.type === 'person'"
class="badge rounded-pill bg-person"
>
{{ trans(PERSON) }}
</span> </span>
<span <span
v-if="entity.type === 'thirdparty'" v-if="props.entity.type === 'thirdparty'"
class="badge rounded-pill bg-thirdparty" class="badge rounded-pill bg-thirdparty"
> >
<template v-if="options.displayLong !== true"> <template v-if="props.options.displayLong !== true">
{{ $t("thirdparty.thirdparty") }} {{ trans(THIRDPARTY) }}
</template> </template>
<i class="fa fa-fw fa-user" v-if="entity.kind === 'child'" /> <i class="fa fa-fw fa-user" v-if="props.entity.kind === 'child'" />
<i <i
class="fa fa-fw fa-hospital-o" class="fa fa-fw fa-hospital-o"
v-else-if="entity.kind === 'company'" v-else-if="props.entity.kind === 'company'"
/> />
<i class="fa fa-fw fa-user-md" v-else /> <i class="fa fa-fw fa-user-md" v-else />
<template v-if="options.displayLong === true"> <template v-if="props.options.displayLong === true">
<span v-if="entity.kind === 'child'">{{ <span v-if="props.entity.kind === 'child'">{{
$t("thirdparty.child") trans(THIRDPARTY_CONTACT_OF)
}}</span> }}</span>
<span v-else-if="entity.kind === 'company'">{{ <span v-else-if="props.entity.kind === 'company'">{{
$t("thirdparty.company") trans(THIRDPARTY_A_CONTACT)
}}</span> }}</span>
<span v-else>{{ $t("thirdparty.contact") }}</span> <span v-else>{{ $t("thirdparty.contact") }}</span>
</template> </template>
</span> </span>
<span v-if="entity.type === 'user'" class="badge rounded-pill bg-user"> <span
{{ $t("user") }} v-if="props.entity.type === 'user'"
class="badge rounded-pill bg-user"
>
{{ trans(ACCEPTED_USERS) }}
</span> </span>
<span v-if="entity.type === 'household'" class="badge rounded-pill bg-user"> <span
{{ $t("household") }} v-if="props.entity.type === 'household'"
class="badge rounded-pill bg-user"
>
{{ trans(HOUSEHOLD) }}
</span> </span>
</template> </template>
<script> <script setup>
export default { import {
name: "BadgeEntity", trans,
props: ["options", "entity"], HOUSEHOLD,
i18n: { ACCEPTED_USERS,
messages: { THIRDPARTY_A_CONTACT,
fr: { THIRDPARTY_CONTACT_OF,
person: "Usager", PERSON,
thirdparty: { THIRDPARTY,
thirdparty: "Tiers", } from "translator";
child: "Personne de contact",
company: "Personne morale", const props = defineProps({
contact: "Personne physique", options: Object,
}, entity: Object,
user: "TMS", });
household: "Ménage",
},
},
},
};
</script> </script>

View File

@@ -66,13 +66,13 @@
<div v-if="useDatePane === true" class="address-more"> <div v-if="useDatePane === true" class="address-more">
<div v-if="address.validFrom"> <div v-if="address.validFrom">
<span class="validFrom"> <span class="validFrom">
<b>{{ $t("validFrom") }}</b <b>{{ trans(ADDRESS_VALID_FROM) }}</b
>: {{ $d(address.validFrom.date) }} >: {{ $d(address.validFrom.date) }}
</span> </span>
</div> </div>
<div v-if="address.validTo"> <div v-if="address.validTo">
<span class="validTo"> <span class="validTo">
<b>{{ $t("validTo") }}</b <b>{{ trans(ADDRESS_VALID_TO) }}</b
>: {{ $d(address.validTo.date) }} >: {{ $d(address.validTo.date) }}
</span> </span>
</div> </div>
@@ -83,6 +83,7 @@
<script> <script>
import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue"; import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue";
import AddressDetailsButton from "ChillMainAssets/vuejs/_components/AddressDetails/AddressDetailsButton.vue"; import AddressDetailsButton from "ChillMainAssets/vuejs/_components/AddressDetails/AddressDetailsButton.vue";
import { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO } from "translator";
export default { export default {
name: "AddressRenderBox", name: "AddressRenderBox",
@@ -107,6 +108,9 @@ export default {
type: Boolean, type: Boolean,
}, },
}, },
setup() {
return { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO };
},
computed: { computed: {
component() { component() {
return this.isMultiline === true ? "div" : "span"; return this.isMultiline === true ? "div" : "span";

View File

@@ -6,8 +6,8 @@
v-if="!subscriberFinal" v-if="!subscriberFinal"
@click="subscribeTo('subscribe', 'final')" @click="subscribeTo('subscribe', 'final')"
> >
<i class="fa fa-check fa-fw" /> <i class="fa fa-check fa-fw"></i>
{{ $t("subscribe_final") }} {{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}
</button> </button>
<button <button
class="btn btn-misc" class="btn btn-misc"
@@ -15,8 +15,8 @@
v-if="subscriberFinal" v-if="subscriberFinal"
@click="subscribeTo('unsubscribe', 'final')" @click="subscribeTo('unsubscribe', 'final')"
> >
<i class="fa fa-times fa-fw" /> <i class="fa fa-times fa-fw"></i>
{{ $t("unsubscribe_final") }} {{ trans(WORKFLOW_UNSUBSCRIBE_FINAL) }}
</button> </button>
<button <button
class="btn btn-misc" class="btn btn-misc"
@@ -24,8 +24,8 @@
v-if="!subscriberStep" v-if="!subscriberStep"
@click="subscribeTo('subscribe', 'step')" @click="subscribeTo('subscribe', 'step')"
> >
<i class="fa fa-check fa-fw" /> <i class="fa fa-check fa-fw"></i>
{{ $t("subscribe_all_steps") }} {{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}
</button> </button>
<button <button
class="btn btn-misc" class="btn btn-misc"
@@ -33,31 +33,25 @@
v-if="subscriberStep" v-if="subscriberStep"
@click="subscribeTo('unsubscribe', 'step')" @click="subscribeTo('unsubscribe', 'step')"
> >
<i class="fa fa-times fa-fw" /> <i class="fa fa-times fa-fw"></i>
{{ $t("unsubscribe_all_steps") }} {{ trans(WORKFLOW_UNSUBSCRIBE_ALL_STEPS) }}
</button> </button>
</div> </div>
</template> </template>
<script> <script setup>
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
import { defineProps, defineEmits } from "vue";
import {
trans,
WORKFLOW_SUBSCRIBE_FINAL,
WORKFLOW_UNSUBSCRIBE_FINAL,
WORKFLOW_SUBSCRIBE_ALL_STEPS,
WORKFLOW_UNSUBSCRIBE_ALL_STEPS,
} from "translator";
export default { // props
name: "EntityWorkflowVueSubscriber", const props = defineProps({
i18n: {
messages: {
fr: {
subscribe_final: "Recevoir une notification à l'étape finale",
unsubscribe_final:
"Ne plus recevoir de notification à l'étape finale",
subscribe_all_steps:
"Recevoir une notification à chaque étape du suivi",
unsubscribe_all_steps:
"Ne plus recevoir de notification à chaque étape du suivi",
},
},
},
props: {
entityWorkflowId: { entityWorkflowId: {
type: Number, type: Number,
required: true, required: true,
@@ -70,57 +64,24 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
}, });
emits: ["subscriptionUpdated"],
methods: { //methods
subscribeTo(step, to) { const subscribeTo = (step, to) => {
let params = new URLSearchParams(); let params = new URLSearchParams();
params.set("subscribe", to); params.set("subscribe", to);
const url = const url =
`/api/1.0/main/workflow/${this.entityWorkflowId}/${step}?` + `/api/1.0/main/workflow/${props.entityWorkflowId}/${step}?` +
params.toString(); params.toString();
makeFetch("POST", url).then((response) => { makeFetch("POST", url).then((response) => {
this.$emit("subscriptionUpdated", response); emit("subscriptionUpdated", response);
}); });
},
},
}; };
/*
* ALTERNATIVES
*
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="laststep">
<label class="form-check-label" for="laststep">{{ $t('subscribe_final') }}</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="allsteps">
<label class="form-check-label" for="allsteps">{{ $t('subscribe_all_steps') }}</label>
</div>
<div class="list-group my-3"> // emit
<label class="list-group-item"> const emit = defineEmits(["subscriptionUpdated"]);
<input class="form-check-input me-1" type="checkbox" value="">
{{ $t('subscribe_final') }}
</label>
<label class="list-group-item">
<input class="form-check-input me-1" type="checkbox" value="">
{{ $t('subscribe_all_steps') }}
</label>
</div>
<div class="btn-group-vertical my-3" role="group">
<button type="button" class="btn btn-outline-primary">
<i class="fa fa-check fa-fw"></i>
{{ $t('subscribe_final') }}
</button>
<button type="button" class="btn btn-outline-primary">
<i class="fa fa-check fa-fw"></i>
{{ $t('subscribe_all_steps') }}
</button>
</div>
*/
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="flex-table workflow" id="workflow-list"> <div class="flex-table workflow" id="workflow-list">
<div <div
v-for="(w, i) in workflows" v-for="(w, i) in props.workflows"
:key="`workflow-${i}`" :key="`workflow-${i}`"
class="item-bloc" class="item-bloc"
> >
@@ -48,7 +48,7 @@
<span <span
v-if="w.isOnHoldAtCurrentStep" v-if="w.isOnHoldAtCurrentStep"
class="badge bg-success rounded-pill" class="badge bg-success rounded-pill"
>{{ $t("on_hold") }}</span >{{ trans(WORKFLOW_ON_HOLD) }}</span
> >
</div> </div>
@@ -56,11 +56,11 @@
<div class="item-col flex-grow-1"> <div class="item-col flex-grow-1">
<p v-if="isUserSubscribedToStep(w)"> <p v-if="isUserSubscribedToStep(w)">
<i class="fa fa-check fa-fw"></i> <i class="fa fa-check fa-fw"></i>
{{ $t("you_subscribed_to_all_steps") }} {{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS) }}
</p> </p>
<p v-if="isUserSubscribedToFinal(w)"> <p v-if="isUserSubscribedToFinal(w)">
<i class="fa fa-check fa-fw"></i> <i class="fa fa-check fa-fw"></i>
{{ $t("you_subscribed_to_final_step") }} {{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP) }}
</p> </p>
</div> </div>
<div class="item-col"> <div class="item-col">
@@ -69,7 +69,7 @@
<a <a
:href="goToUrl(w)" :href="goToUrl(w)"
class="btn btn-sm btn-show" class="btn btn-sm btn-show"
:title="$t('action.show')" :title="trans(SEE)"
></a> ></a>
</li> </li>
</ul> </ul>
@@ -79,37 +79,30 @@
</div> </div>
</template> </template>
<script> <script setup>
import Popover from "bootstrap/js/src/popover"; import Popover from "bootstrap/js/src/popover";
import { onMounted } from "vue";
import {
trans,
BY_USER,
SEE,
WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS,
WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP,
WORKFLOW_ON_HOLD,
WORKFLOW_AT,
} from "translator";
const i18n = { // props
messages: { const props = defineProps({
fr: {
you_subscribed_to_all_steps:
"Vous recevrez une notification à chaque étape",
you_subscribed_to_final_step:
"Vous recevrez une notification à l'étape finale",
by: "Par",
at: "Le",
on_hold: "En attente",
},
},
};
export default {
name: "ListWorkflow",
i18n: i18n,
props: {
workflows: { workflows: {
type: Array, type: Array,
required: true, required: true,
}, },
}, });
methods: {
goToUrl(w) { // methods
return `/fr/main/workflow/${w.id}/show`; const goToUrl = (w) => `/fr/main/workflow/${w.id}/show`;
}, const getPopTitle = (step) => {
getPopTitle(step) {
if (step.transitionPrevious != null) { if (step.transitionPrevious != null) {
//console.log(step.transitionPrevious.text); //console.log(step.transitionPrevious.text);
let freezed = step.isFreezed let freezed = step.isFreezed
@@ -117,47 +110,34 @@ export default {
: ``; : ``;
return `${freezed}${step.transitionPrevious.text}`; return `${freezed}${step.transitionPrevious.text}`;
} }
}, };
getPopContent(step) { const getPopContent = (step) => {
if (step.transitionPrevious != null) { if (step.transitionPrevious != null) {
if (step.transitionPreviousBy !== null) { if (step.transitionPreviousBy !== null) {
return `<ul class="small_in_title"> return `<ul class="small_in_title">
<li><span class="item-key">${i18n.messages.fr.by} : </span><b>${step.transitionPreviousBy.text}</b></li> <li><span class="item-key">${trans(BY_USER)} : </span><b>${step.transitionPreviousBy.text}</b></li>
<li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li> <li><span class="item-key">${trans(WORKFLOW_AT)} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`; </ul>`;
} else { } else {
return `<ul class="small_in_title"> return `<ul class="small_in_title">
<li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li> <li><span class="item-key">${trans(WORKFLOW_AT)} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`; </ul>`;
} }
} }
}, };
formatDate(datetime) { const formatDate = (datetime) =>
return ( datetime.split("T")[0] + " " + datetime.split("T")[1].substring(0, 5);
datetime.split("T")[0] + const isUserSubscribedToStep = () => false;
" " + const isUserSubscribedToFinal = () => false;
datetime.split("T")[1].substring(0, 5)
); onMounted(() => {
},
isUserSubscribedToStep(w) {
// todo
return false;
},
isUserSubscribedToFinal(w) {
// todo
return false;
},
},
mounted() {
const triggerList = [].slice.call( const triggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="popover"]'), document.querySelectorAll('[data-bs-toggle="popover"]'),
); );
const popoverList = triggerList.map(function (el) { triggerList.map(function (el) {
//console.log('popover', el)
return new Popover(el, { return new Popover(el, {
html: true, html: true,
}); });
}); });
}, });
};
</script> </script>

View File

@@ -1,23 +1,24 @@
<template> <template>
<pick-workflow <Pick-workflow
:relatedEntityClass="this.relatedEntityClass" :relatedEntityClass="props.relatedEntityClass"
:relatedEntityId="this.relatedEntityId" :relatedEntityId="props.relatedEntityId"
:workflowsAvailables="workflowsAvailables" :workflowsAvailables="props.workflowsAvailables"
:preventDefaultMoveToGenerate="this.$props.preventDefaultMoveToGenerate" :preventDefaultMoveToGenerate="props.preventDefaultMoveToGenerate"
:goToGenerateWorkflowPayload="this.goToGenerateWorkflowPayload" :goToGenerateWorkflowPayload="props.goToGenerateWorkflowPayload"
:countExistingWorkflows="countWorkflows" :countExistingWorkflows="countWorkflows"
:embedded-within-list-modal="false"
@go-to-generate-workflow="goToGenerateWorkflow" @go-to-generate-workflow="goToGenerateWorkflow"
@click-open-list="openModal" @click-open-list="openModal"
></pick-workflow> ></Pick-workflow>
<teleport to="body"> <teleport to="body">
<modal <Modal
v-if="modal.showModal" v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass" :modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false" @close="modal.showModal = false"
> >
<template v-slot:header> <template v-slot:header>
<h2 class="modal-title">{{ $t("workflow_list") }}</h2> <h2 class="modal-title">{{ trans(WORKFLOW_LIST) }}</h2>
</template> </template>
<template v-slot:body> <template v-slot:body>
@@ -27,38 +28,33 @@
<template v-slot:footer> <template v-slot:footer>
<pick-workflow <pick-workflow
v-if="allowCreate" v-if="allowCreate"
:relatedEntityClass="this.relatedEntityClass" :relatedEntityClass="props.relatedEntityClass"
:relatedEntityId="this.relatedEntityId" :relatedEntityId="props.relatedEntityId"
:workflowsAvailables="workflowsAvailables" :workflowsAvailables="props.workflowsAvailables"
:preventDefaultMoveToGenerate=" :preventDefaultMoveToGenerate="
this.$props.preventDefaultMoveToGenerate props.preventDefaultMoveToGenerate
" "
:goToGenerateWorkflowPayload=" :goToGenerateWorkflowPayload="
this.goToGenerateWorkflowPayload props.goToGenerateWorkflowPayload
" "
:countExistingWorkflows="countWorkflows" :countExistingWorkflows="countWorkflows"
:embedded-within-list-modal="true" :embedded-within-list-modal="true"
@go-to-generate-workflow="this.goToGenerateWorkflow" @go-to-generate-workflow="goToGenerateWorkflow"
></pick-workflow> ></pick-workflow>
</template> </template>
</modal> </Modal>
</teleport> </teleport>
</template> </template>
<script> <script setup>
import { ref, computed, defineProps, defineEmits } from "vue";
import Modal from "ChillMainAssets/vuejs/_components/Modal"; import Modal from "ChillMainAssets/vuejs/_components/Modal";
import PickWorkflow from "ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue"; import PickWorkflow from "ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue";
import ListWorkflowVue from "ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue"; import ListWorkflowVue from "ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue";
import { trans, WORKFLOW_LIST } from "translator";
export default { // Define props
name: "ListWorkflowModal", const props = defineProps({
components: {
Modal,
PickWorkflow,
ListWorkflowVue,
},
emits: ["goToGenerateWorkflow"],
props: {
workflows: { workflows: {
type: Array, type: Array,
required: true, required: true,
@@ -86,44 +82,26 @@ export default {
}, },
goToGenerateWorkflowPayload: { goToGenerateWorkflowPayload: {
required: false, required: false,
default: {}, default: () => ({}),
}, },
}, });
data() {
return { // Define emits
modal: { const emit = defineEmits(["goToGenerateWorkflow"]);
// Reactive data
const modal = ref({
showModal: false, showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl", modalDialogClass: "modal-dialog-scrollable modal-xl",
}, });
};
}, // Computed properties
computed: { const countWorkflows = computed(() => props.workflows.length);
countWorkflows() {
return this.workflows.length; // Methods
}, const openModal = () => (modal.value.showModal = true);
hasWorkflow() {
return this.countWorkflows > 0; const goToGenerateWorkflow = (data) => emit("goToGenerateWorkflow", data);
},
},
methods: {
openModal() {
this.modal.showModal = true;
},
goToGenerateWorkflow(data) {
console.log("go to generate workflow intercepted", data);
this.$emit("goToGenerateWorkflow", data);
},
},
i18n: {
messages: {
fr: {
workflow_list: "Liste des workflows associés",
workflow: " workflow associé",
workflows: " workflows associés",
},
},
},
};
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -8,28 +8,28 @@
aria-modal="true" aria-modal="true"
role="dialog" role="dialog"
> >
<div class="modal-dialog" :class="modalDialogClass"> <div class="modal-dialog" :class="props.modalDialogClass || {}">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<slot name="header" /> <slot name="header"></slot>
<button class="close btn" @click="$emit('close')"> <button class="close btn" @click="emits('close')">
<i class="fa fa-times" aria-hidden="true" /> <i class="fa fa-times" aria-hidden="true"></i>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="body-head"> <div class="body-head">
<slot name="body-head" /> <slot name="body-head"></slot>
</div> </div>
<slot name="body" /> <slot name="body"></slot>
</div> </div>
<div class="modal-footer" v-if="!hideFooter"> <div class="modal-footer" v-if="!hideFooter">
<button <button
class="btn btn-cancel" class="btn btn-cancel"
@click="$emit('close')" @click="emits('close')"
> >
{{ $t("action.close") }} {{ trans(MODAL_ACTION_CLOSE) }}
</button> </button>
<slot name="footer" /> <slot name="footer"></slot>
</div> </div>
</div> </div>
</div> </div>
@@ -39,8 +39,7 @@
</transition> </transition>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from "vue";
/* /*
* This Modal component is a mix between Vue3 modal implementation * This Modal component is a mix between Vue3 modal implementation
* [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden * [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden
@@ -50,22 +49,23 @@ import { defineComponent } from "vue";
* [+] using bootstrap css classes, the modal have a responsive behaviour, * [+] using bootstrap css classes, the modal have a responsive behaviour,
* [+] modal design can be configured using css classes (size, scroll) * [+] modal design can be configured using css classes (size, scroll)
*/ */
export default defineComponent({ import { trans, MODAL_ACTION_CLOSE } from "translator";
name: "Modal", import { defineProps } from "vue";
props: {
modalDialogClass: { export interface ModalProps {
type: Object, modalDialogClass: object | null;
required: false, hideFooter: boolean;
default: {}, }
},
hideFooter: { // Define the props
type: Boolean, const props = withDefaults(defineProps<ModalProps>(), {
required: false, hideFooter: false,
default: false, modalDialogClass: null,
},
},
emits: ["close"],
}); });
const emits = defineEmits<{
close: [];
}>();
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -9,12 +9,12 @@
class="btn" class="btn"
:class="overrideClass" :class="overrideClass"
type="button" type="button"
:title="$t('markAsUnread')" :title="trans(NOTIFICATION_MARK_AS_UNREAD)"
@click="markAsUnread" @click="markAsUnread"
> >
<i class="fa fa-sm fa-envelope-o" /> <i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!buttonNoText" class="ps-2"> <span v-if="!props.buttonNoText" class="ps-2">
{{ $t("markAsUnread") }} {{ trans(NOTIFICATION_MARK_AS_UNREAD) }}
</span> </span>
</button> </button>
@@ -23,12 +23,12 @@
class="btn" class="btn"
:class="overrideClass" :class="overrideClass"
type="button" type="button"
:title="$t('markAsRead')" :title="trans(NOTIFICATION_MARK_AS_READ)"
@click="markAsRead" @click="markAsRead"
> >
<i class="fa fa-sm fa-envelope-open-o" /> <i class="fa fa-sm fa-envelope-open-o"></i>
<span v-if="!buttonNoText" class="ps-2"> <span v-if="!buttonNoText" class="ps-2">
{{ $t("markAsRead") }} {{ trans(NOTIFICATION_MARK_AS_READ) }}
</span> </span>
</button> </button>
@@ -37,9 +37,9 @@
type="button" type="button"
class="btn btn-outline-primary" class="btn btn-outline-primary"
:href="showUrl" :href="showUrl"
:title="$t('action.show')" :title="trans(SEE)"
> >
<i class="fa fa-sm fa-comment-o" /> <i class="fa fa-sm fa-comment-o"></i>
</a> </a>
<!-- "Mark All Read" button --> <!-- "Mark All Read" button -->
@@ -51,7 +51,7 @@
:title="$t('markAllRead')" :title="$t('markAllRead')"
@click="markAllRead" @click="markAllRead"
> >
<i class="fa fa-sm fa-envelope-o" /> <i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!buttonNoText" class="ps-2"> <span v-if="!buttonNoText" class="ps-2">
{{ $t("markAllRead") }} {{ $t("markAllRead") }}
</span> </span>
@@ -59,89 +59,66 @@
</div> </div>
</template> </template>
<script> <script setup>
import { computed } from "vue";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
import {
trans,
NOTIFICATION_MARK_AS_READ,
NOTIFICATION_MARK_AS_UNREAD,
SEE,
} from "translator";
export default { // Props
name: "NotificationReadToggle", const props = defineProps({
props: {
isRead: { isRead: {
required: true,
type: Boolean, type: Boolean,
required: true,
}, },
notificationId: { notificationId: {
required: true,
type: Number, type: Number,
required: true,
}, },
// Optional
buttonClass: { buttonClass: {
required: false,
type: String, type: String,
required: false,
}, },
buttonNoText: { buttonNoText: {
required: false,
type: Boolean, type: Boolean,
required: false,
}, },
showUrl: { showUrl: {
required: false,
type: String, type: String,
required: false,
}, },
}, });
emits: ["markRead", "markUnread"],
computed: { // Emits
/// [Option] override default button appearance (btn-misc) const emit = defineEmits(["markRead", "markUnread"]);
overrideClass() {
return this.buttonClass ? this.buttonClass : "btn-misc"; // Computed
}, const overrideClass = computed(() => props.buttonClass || "btn-misc");
/// [Option] don't display text on button const isButtonGroup = computed(() => props.showUrl);
buttonHideText() {
return this.buttonNoText; // Methods
}, const markAsUnread = () => {
/// [Option] showUrl is href for show page second button.
// When passed, the component return a button-group with 2 buttons.
isButtonGroup() {
return this.showUrl;
},
},
methods: {
markAsUnread() {
makeFetch( makeFetch(
"POST", "POST",
`/api/1.0/main/notification/${this.notificationId}/mark/unread`, `/api/1.0/main/notification/${props.notificationId}/mark/unread`,
[], [],
).then(() => { ).then(() => {
this.$emit("markRead", { notificationId: this.notificationId }); emit("markRead", { notificationId: props.notificationId });
}); });
}, };
markAsRead() {
const markAsRead = () => {
makeFetch( makeFetch(
"POST", "POST",
`/api/1.0/main/notification/${this.notificationId}/mark/read`, `/api/1.0/main/notification/${props.notificationId}/mark/read`,
[], [],
).then(() => { ).then(() => {
this.$emit("markUnread", { emit("markUnread", { notificationId: props.notificationId });
notificationId: this.notificationId,
}); });
});
},
markAllRead() {
makeFetch(
"POST",
`/api/1.0/main/notification/markallread`,
[],
).then(() => {
this.$emit("markAllRead");
});
},
},
i18n: {
messages: {
fr: {
markAsUnread: "Marquer comme non-lu",
markAsRead: "Marquer comme lu",
},
},
},
}; };
</script> </script>

View File

@@ -8,10 +8,10 @@
]" ]"
@click="openModal" @click="openModal"
> >
<i v-if="isChangeIcon" class="fa me-2" :class="options.changeIcon" /> <i v-if="isChangeIcon" class="fa me-2" :class="options.changeIcon"></i>
<span v-if="!noText"> <span v-if="!noText">
{{ $t("online_edit_document") }} {{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }}
</span> </span>
</a> </a>
@@ -19,8 +19,8 @@
<div class="wopi-frame" v-if="isOpenDocument"> <div class="wopi-frame" v-if="isOpenDocument">
<modal <modal
v-if="modal.showModal" v-if="modal.showModal"
:modal-dialog-class="modal.modalDialogClass" :modalDialogClass="modal.modalDialogClass"
:hide-footer="true" :hideFooter="true"
@close="modal.showModal = false" @close="modal.showModal = false"
> >
<template #header> <template #header>
@@ -28,54 +28,51 @@
<span class="ms-auto me-3"> <span class="ms-auto me-3">
<span v-if="options.title">{{ options.title }}</span> <span v-if="options.title">{{ options.title }}</span>
</span> </span>
<!--
<a class="btn btn-outline-light">
<i class="fa fa-save fa-fw"></i>
{{ $t('save_and_quit') }}
</a>
-->
</template> </template>
<template #body> <template #body>
<div v-if="loading" class="loading"> <div v-if="loading" class="loading">
<i <i
class="fa fa-circle-o-notch fa-spin fa-3x" class="fa fa-circle-o-notch fa-spin fa-3x"
:title="$t('loading')" :title="trans(WOPI_LOADING)"
/> ></i>
</div> </div>
<iframe :src="this.wopiUrl" @load="loaded" /> <iframe :src="this.wopiUrl" @load="loaded"></iframe>
</template> </template>
</modal> </modal>
</div> </div>
<div v-else> <div v-else>
<modal <Modal
v-if="modal.showModal" v-if="modal.showModal"
modal-dialog-class="modal-sm" modalDialogClass="modal-sm"
@close="modal.showModal = false" @close="modal.showModal = false"
> >
<template #header> <template v-slot:header>
<h3>{{ $t("invalid_title") }}</h3> <h3>{{ trans(WOPI_INVALID_TITLE) }}</h3>
</template> </template>
<template #body> <template v-slot:body>
<div class="alert alert-warning"> <div class="alert alert-warning">
{{ $t("invalid_message") }} {{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }}
</div> </div>
</template> </template>
</modal> </Modal>
</div> </div>
</teleport> </teleport>
</template> </template>
<script> <script setup>
import { ref, computed } from "vue";
import {
trans,
WOPI_ONLINE_EDIT_DOCUMENT,
WOPI_INVALID_TITLE,
WOPI_LOADING,
} from "translator";
import Modal from "ChillMainAssets/vuejs/_components/Modal"; import Modal from "ChillMainAssets/vuejs/_components/Modal";
import logo from "ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png"; import logo from "ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png";
export default { // Props
name: "OpenWopiLink", const props = defineProps({
components: {
Modal,
},
props: {
wopiUrl: { wopiUrl: {
type: String, type: String,
required: true, required: true,
@@ -88,16 +85,17 @@ export default {
type: Object, type: Object,
required: false, required: false,
}, },
}, });
data() {
return { // data
modal: { const modal = ref({
showModal: false, showModal: false,
modalDialogClass: "modal-fullscreen", //modal-dialog-scrollable modalDialogClass: "modal-fullscreen",
}, });
logo: logo, const loading = ref(false);
loading: false,
mime: [ // MIME types
const mime = [
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query // TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
"application/clarisworks", "application/clarisworks",
"application/coreldraw", "application/coreldraw",
@@ -169,62 +167,25 @@ export default {
"application/x-pagemaker", "application/x-pagemaker",
"application/x-sony-bbeb", "application/x-sony-bbeb",
"application/x-t602", "application/x-t602",
], ];
};
}, // Computed
computed: { const isOpenDocument = computed(() => mime.includes(props.type));
isOpenDocument() {
if (this.mime.indexOf(this.type) !== -1) { const noText = computed(() => props.options?.noText === true);
return true;
} const isChangeIcon = computed(() => !!props.options?.changeIcon);
return false;
}, const isChangeClass = computed(() => !!props.options?.changeClass);
noText() {
if (typeof this.options.noText !== "undefined") { // Methods
return this.options.noText === true; const openModal = () => {
} loading.value = true;
return false; modal.value.showModal = true;
}, };
isChangeIcon() {
if (typeof this.options.changeIcon !== "undefined") { const loaded = () => {
return !( loading.value = false;
this.options.changeIcon === null ||
this.options.changeIcon === ""
);
}
return false;
},
isChangeClass() {
if (typeof this.options.changeClass !== "undefined") {
return !(
this.options.changeClass === null ||
this.options.changeClass === ""
);
}
return false;
},
},
methods: {
openModal() {
this.loading = true;
this.modal.showModal = true;
},
loaded() {
this.loading = false;
},
},
i18n: {
messages: {
fr: {
online_edit_document: "Éditer en ligne",
save_and_quit: "Enregistrer et quitter",
loading: "Chargement de l'éditeur en ligne",
invalid_title: "Format incompatible",
invalid_message:
"Désolé, ce format de document n'est pas éditable en ligne.",
},
},
},
}; };
</script> </script>

View File

@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Workflow\EventSubscriber;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Entity\StoredObjectPointInTime;
use Chill\DocStoreBundle\Entity\StoredObjectPointInTimeReasonEnum;
use Chill\DocStoreBundle\Service\StoredObjectRestoreInterface;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
use Chill\MainBundle\Workflow\EventSubscriber\OnCancelRestoreDocumentToEditableEventSubscriber;
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Workflow\DefinitionBuilder;
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\WorkflowInterface;
/**
* @internal
*
* @coversNothing
*/
class OnCancelRestoreDocumentToEditableEventSubscriberTest extends TestCase
{
private function buildRegistry(StoredObjectRestoreInterface $storedObjectRestore, ?StoredObject $storedObject): Registry
{
$builder = new DefinitionBuilder(
['initial', 'intermediate', 'final', 'cancel'],
[
new Transition('to_intermediate', ['initial'], ['intermediate']),
new Transition('intermediate_to_final', ['intermediate'], ['final']),
new Transition('to_final', ['initial'], ['final']),
new Transition('to_cancel', ['initial'], ['cancel']),
]
);
$builder->setMetadataStore(
new InMemoryMetadataStore(
placesMetadata: [
'final' => ['isFinal' => true],
'cancel' => ['isFinal' => true, 'isFinalPositive' => false],
]
)
);
$registry = new Registry();
$workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), $eventDispatcher = new EventDispatcher(), 'dummy');
$manager = $this->createMock(EntityWorkflowManager::class);
$manager->method('getAssociatedStoredObject')->willReturn($storedObject);
$eventSubscriber = new OnCancelRestoreDocumentToEditableEventSubscriber(
$registry,
$manager,
$storedObjectRestore
);
$eventDispatcher->addSubscriber($eventSubscriber);
$registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface {
public function supports(WorkflowInterface $workflow, object $subject): bool
{
return true;
}
});
return $registry;
}
public function testOnCancelRestoreDocumentToEditableExpectsRestoring(): void
{
$storedObject = new StoredObject();
$version = $storedObject->registerVersion();
new StoredObjectPointInTime($version, StoredObjectPointInTimeReasonEnum::KEEP_BEFORE_CONVERSION);
$storedObject->registerVersion();
$restore = $this->createMock(StoredObjectRestoreInterface::class);
$restore->expects($this->once())->method('restore')->with($version);
$registry = $this->buildRegistry($restore, $storedObject);
$entityWorkflow = (new EntityWorkflow())->setWorkflowName('dummy');
$workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
$context = new WorkflowTransitionContextDTO($entityWorkflow);
$workflow->apply($entityWorkflow, 'to_cancel', [
'context' => $context,
'transition' => 'to_cancel',
'transitionAt' => new \DateTimeImmutable('now'),
]);
}
public function testOnCancelRestoreDocumentDoNotExpectRestoring(): void
{
$storedObject = new StoredObject();
$version = $storedObject->registerVersion();
new StoredObjectPointInTime($version, StoredObjectPointInTimeReasonEnum::KEEP_BEFORE_CONVERSION);
$storedObject->registerVersion();
$restore = $this->createMock(StoredObjectRestoreInterface::class);
$restore->expects($this->never())->method('restore')->withAnyParameters();
$registry = $this->buildRegistry($restore, $storedObject);
$entityWorkflow = (new EntityWorkflow())->setWorkflowName('dummy');
$workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
$context = new WorkflowTransitionContextDTO($entityWorkflow);
$workflow->apply($entityWorkflow, 'to_intermediate', [
'context' => $context,
'transition' => 'to_intermediate',
'transitionAt' => new \DateTimeImmutable('now'),
]);
$workflow->apply($entityWorkflow, 'intermediate_to_final', [
'context' => $context,
'transition' => 'intermediate_to_final',
'transitionAt' => new \DateTimeImmutable('now'),
]);
}
public function testOnCancelRestoreDocumentToEditableToCancelStoredObjectWithoutKepts(): void
{
$storedObject = new StoredObject();
$storedObject->registerVersion();
$restore = $this->createMock(StoredObjectRestoreInterface::class);
$restore->expects($this->never())->method('restore')->withAnyParameters();
$registry = $this->buildRegistry($restore, $storedObject);
$entityWorkflow = (new EntityWorkflow())->setWorkflowName('dummy');
$workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
$context = new WorkflowTransitionContextDTO($entityWorkflow);
$workflow->apply($entityWorkflow, 'to_cancel', [
'context' => $context,
'transition' => 'to_cancel',
'transitionAt' => new \DateTimeImmutable('now'),
]);
}
}

View File

@@ -0,0 +1,71 @@
<?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\Workflow\EventSubscriber;
use Chill\DocStoreBundle\Service\StoredObjectRestoreInterface;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\TransitionEvent;
use Symfony\Component\Workflow\Registry;
final readonly class OnCancelRestoreDocumentToEditableEventSubscriber implements EventSubscriberInterface
{
public function __construct(
private Registry $registry,
private EntityWorkflowManager $manager,
private StoredObjectRestoreInterface $storedObjectRestore,
) {}
public static function getSubscribedEvents(): array
{
return ['workflow.transition' => ['onCancelRestoreDocumentToEditable', 0]];
}
public function onCancelRestoreDocumentToEditable(TransitionEvent $event): void
{
$entityWorkflow = $event->getSubject();
if (!$entityWorkflow instanceof EntityWorkflow) {
return;
}
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
foreach ($event->getTransition()->getTos() as $place) {
$metadata = $workflow->getMetadataStore()->getPlaceMetadata($place);
if (($metadata['isFinal'] ?? false) && !($metadata['isFinalPositive'] ?? true)) {
$this->restoreDocument($entityWorkflow);
return;
}
}
}
private function restoreDocument(EntityWorkflow $entityWorkflow): void
{
$storedObject = $this->manager->getAssociatedStoredObject($entityWorkflow);
if (null === $storedObject) {
return;
}
$version = $storedObject->getLastKeptBeforeConversionVersion();
if (null === $version) {
return;
}
$this->storedObjectRestore->restore($storedObject->getLastKeptBeforeConversionVersion());
}
}

View File

@@ -1,45 +1,3 @@
const { styles } = require("@ckeditor/ckeditor5-dev-utils");
const {
CKEditorTranslationsPlugin,
} = require("@ckeditor/ckeditor5-dev-translations");
buildCKEditor = function (encore) {
encore
.addPlugin(
new CKEditorTranslationsPlugin({
language: "fr",
addMainLanguageTranslationsToAllAssets: true,
verbose: !encore.isProduction(),
strict: true,
}),
)
// Use raw-loader for CKEditor 5 SVG files.
.addRule({
test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
loader: "raw-loader",
})
// Configure other image loaders to exclude CKEditor 5 SVG files.
.configureLoaderRule("images", (loader) => {
loader.exclude =
/ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/;
})
// Configure PostCSS loader.
.addLoader({
test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
loader: "postcss-loader",
options: {
postcssOptions: styles.getPostCssConfig({
themeImporter: {
themePath: require.resolve("@ckeditor/ckeditor5-theme-lark"),
},
minify: true,
}),
},
});
};
// Compile and loads all assets from the Chill Main Bundle // Compile and loads all assets from the Chill Main Bundle
module.exports = function (encore, entries) { module.exports = function (encore, entries) {
@@ -79,8 +37,6 @@ module.exports = function (encore, entries) {
__dirname + "/Resources/public/page/export/download-export.js", __dirname + "/Resources/public/page/export/download-export.js",
); );
buildCKEditor(encore);
// Modules entrypoints // Modules entrypoints
encore.addEntry( encore.addEntry(
"mod_collection", "mod_collection",

View File

@@ -2,6 +2,7 @@ services:
_defaults: _defaults:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
public: false
Chill\MainBundle\Controller\: Chill\MainBundle\Controller\:
resource: '../../Controller' resource: '../../Controller'

View File

@@ -44,6 +44,9 @@ address_fields: Données liées à l'adresse
Datas: Données Datas: Données
No title: Aucun titre No title: Aucun titre
icon: icône icon: icône
See: Voir
Name: Nom
Label: Nom
user: user:
profile: profile:
@@ -124,6 +127,49 @@ address:
address_homeless: L'adresse est-elle celle d'un domicile fixe ? address_homeless: L'adresse est-elle celle d'un domicile fixe ?
real address: Adresse d'un domicile real address: Adresse d'un domicile
consider homeless: Cette adresse est incomplète consider homeless: Cette adresse est incomplète
add_an_address_title: Créer une adresse
edit_an_address_title: Modifier une adresse
create_a_new_address: Créer une nouvelle adresse
edit_address: Modifier l'adresse
select_an_address_title: Sélectionner une adresse
fill_an_address: Compléter l'adresse
select_country: Choisir le pays
country: Pays
select_city: Choisir une localité
city: Localité
other_city: Autre localité
select_address: Choisir une adresse
address: Adresse
other_address: Autre adresse
create_address: Adresse inconnue. Cliquez ici pour créer une nouvelle adresse
isNoAddress: Pas d'adresse complète
isConfidential: Adresse confidentielle
street: Nom de rue
streetNumber: Numéro
floor: Étage
corridor: Couloir
steps: Escalier
flat: Appartement
buildingName: Résidence
extra: Complément d'adresse
distribution: Cedex
create_postal_code: Localité inconnue. Cliquez ici pour créer une nouvelle localité
postalCode_name: Nom
postalCode_code: Code postal
date: Date de la nouvelle adresse
valid_from: L'adresse est valable à partir du
valid_to: L'adresse est valable jusqu'au
back_to_the_list: Retour à la liste
loading: chargement en cours...
address_suggestions: Suggestion d'adresses
address_new_success: La nouvelle adresse est enregistrée.
address_edit_success: L'adresse a été mise à jour.
wait_redirection: La page est redirigée.
not_yet_address: Il n'y a pas encore d'adresse. Cliquez sur '+ Créer une adresse'
use_this_address: Utiliser cette adresse
household:
move_date: Date du déménagement
address more: address more:
floor: ét floor: ét
corridor: coul corridor: coul
@@ -508,6 +554,8 @@ Follow workflow: Suivre la décision
Workflow history: Historique de la décision Workflow history: Historique de la décision
workflow: workflow:
list: Liste des workflows associés
associated: workflow associé
deleted: Workflow supprimé deleted: Workflow supprimé
Created by: Créé par Created by: Créé par
My decision: Ma décision My decision: Ma décision
@@ -553,6 +601,7 @@ workflow:
Previous workflow transitionned help: Workflows où vous avez exécuté une action. Previous workflow transitionned help: Workflows où vous avez exécuté une action.
For: Pour For: Pour
Cc: Cc Cc: Cc
At: Le
You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire. You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire.
An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces adresses An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces adresses
Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtenu l'accès grâce au lien reçu par email Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtenu l'accès grâce au lien reçu par email
@@ -575,6 +624,12 @@ workflow:
public_views_by_ip: Visualisation par adresse IP public_views_by_ip: Visualisation par adresse IP
May not associate a document: Le workflow ne concerne pas un document May not associate a document: Le workflow ne concerne pas un document
subscribe_final: Recevoir une notification à l'étape finale
unsubscribe_final: Ne plus recevoir de notification à l'étape finale
subscribe_all_steps: Recevoir une notification à chaque étape du suivi
unsubscribe_all_steps: Ne plus recevoir de notification à chaque étape du suivi
public_link: public_link:
expired_link_title: Lien expiré expired_link_title: Lien expiré
expired_link_explanation: Le lien a expiré, vous ne pouvez plus visualiser ce document. expired_link_explanation: Le lien a expiré, vous ne pouvez plus visualiser ce document.
@@ -656,6 +711,10 @@ notification:
Remove an email: Supprimer l'adresse email Remove an email: Supprimer l'adresse email
Email with access link: Adresse email ayant reçu un lien d'accès Email with access link: Adresse email ayant reçu un lien d'accès
mark_as_read: Marquer comme lu
mark_as_unread: Marquer comme non-lu
export: export:
address_helper: address_helper:
id: Identifiant de l'adresse id: Identifiant de l'adresse
@@ -676,6 +735,25 @@ export:
steps: Escaliers steps: Escaliers
_lat: Latitude _lat: Latitude
_lon: Longitude _lon: Longitude
social_action_list:
id: Identifiant de l'action
social_issue_id: Identifiant de la problématique sociale
social_issue: Problématique sociale
social_issue_ordering: Ordre de la problématique sociale
action_label: Action d'accompagnement
action_ordering: Ordre
goal_label: Objectif
goal_id: Identifiant de l'objectif
goal_result_label: Résultat
goal_result_id: Identifiant du résultat
result_without_goal_label: Résultat (sans objectif)
result_without_goal_id: Identifiant du résultat (sans objectif)
evaluation_title: Évaluation
evaluation_id: Identifiant de l'évaluation
evaluation_url: Adresse URL externe (évaluation)
evaluation_delay_month: Délai de notification (mois)
evaluation_delay_week: Délai de notification (semaine)
evaluation_delay_day: Délai de notification (jours)
rolling_date: rolling_date:
year_previous_start: Début de l'année précédente year_previous_start: Début de l'année précédente
@@ -795,4 +873,43 @@ gender:
Select gender translation: Traduction grammaticale Select gender translation: Traduction grammaticale
Select gender icon: Icône à utiliser Select gender icon: Icône à utiliser
wopi:
online_edit_document: Éditer en ligne
save_and_quit: Enregistrer et quitter
loading: Chargement de l'éditeur en ligne
invalid_title: Format incompatible
invalid_message: Désolé, ce format de document n'est pas éditable en ligne.
onthefly:
show:
person: Détails de l'usager
thirdparty: Détails du tiers
file_person: Ouvrir la fiche de l'usager
file_thirdparty: Voir le Tiers
edit:
person: Modifier un usager
thirdparty: Modifier un tiers
create:
button: Créer {q}
title:
default: Création d'un nouvel usager ou d'un tiers professionnel
person: Création d'un nouvel usager
thirdparty: Création d'un nouveau tiers professionnel
person: un nouvel usager
thirdparty: un nouveau tiers professionnel
addContact:
title: Créer un contact pour {q}
resource_comment_title: Un commentaire est associé à cet interlocuteur
modal:
action:
close: Fermer
multiselect:
placeholder: Choisir
tag_placeholder: Créer un nouvel élément
select_label: Entrée ou cliquez pour sélectionner
deselect_label: Entrée ou cliquez pour désélectionner
select_group_label: Appuyer sur "Entrée" pour sélectionner ce groupe
deselect_group_label: Appuyer sur "Entrée" pour désélectionner ce groupe
selected_label: Sélectionné'

View File

@@ -25,10 +25,11 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent; use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
@@ -46,7 +47,10 @@ use Symfony\Component\Workflow\Registry;
final class AccompanyingCourseApiController extends ApiController final class AccompanyingCourseApiController extends ApiController
{ {
public function __construct(private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, private readonly AccompanyingPeriodACLAwareRepository $accompanyingPeriodACLAwareRepository, private readonly EventDispatcherInterface $eventDispatcher, private readonly ReferralsSuggestionInterface $referralAvailable, private readonly Registry $registry, private readonly ValidatorInterface $validator, private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry) {} public function __construct(private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, private readonly EventDispatcherInterface $eventDispatcher, private readonly ReferralsSuggestionInterface $referralAvailable, private readonly Registry $registry, private readonly ValidatorInterface $validator, protected ManagerRegistry $managerRegistry, private readonly AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository)
{
parent::__construct($managerRegistry);
}
public function commentApi($id, Request $request, string $_format): Response public function commentApi($id, Request $request, string $_format): Response
{ {
@@ -305,6 +309,20 @@ final class AccompanyingCourseApiController extends ApiController
return $this->json($accompanyingCourse->getIntensity(), Response::HTTP_OK, [], ['groups' => ['read']]); return $this->json($accompanyingCourse->getIntensity(), Response::HTTP_OK, [], ['groups' => ['read']]);
} }
/**
* @ParamConverter("accompanyingPeriod", options={"id": "id"})
*/
#[Route(path: '/api/1.0/person/accompanying-course/{id}/works.json', name: 'chill_api_person_accompanying_period_works')]
public function worksByAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): JsonResponse
{
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingPeriod);
$works = $this->accompanyingPeriodWorkRepository->findBy(['accompanyingPeriod' => $accompanyingPeriod]);
dump($works);
return $this->json($works, Response::HTTP_OK, [], ['groups' => ['read']]);
}
public function workApi($id, Request $request, string $_format): Response public function workApi($id, Request $request, string $_format): Response
{ {
return $this->addRemoveSomething( return $this->addRemoveSomething(

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Chill\MainBundle\Serializer\Model\Counter; use Chill\MainBundle\Serializer\Model\Counter;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -27,7 +28,10 @@ class AccompanyingCourseWorkApiController extends ApiController
public function __construct( public function __construct(
private readonly AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository, private readonly AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository,
private readonly Security $security, private readonly Security $security,
) {} ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
}
#[Route(path: '/api/1.0/person/accompanying-period/work/my-near-end')] #[Route(path: '/api/1.0/person/accompanying-period/work/my-near-end')]
public function myWorksNearEndDate(Request $request): JsonResponse public function myWorksNearEndDate(Request $request): JsonResponse

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller; namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
class AccompanyingPeriodCommentApiController extends ApiController {} class AccompanyingPeriodCommentApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller; namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
class AccompanyingPeriodResourceApiController extends ApiController {} class AccompanyingPeriodResourceApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
class AdministrativeStatusController extends CRUDController
{
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
$query->addOrderBy('e.order', 'ASC');
return parent::orderQuery($action, $query, $request, $paginator);
}
}

View File

@@ -22,6 +22,7 @@ use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent;
use Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface; use Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface;
use Chill\PersonBundle\Repository\Household\HouseholdRepository; use Chill\PersonBundle\Repository\Household\HouseholdRepository;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter; use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -31,7 +32,10 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class HouseholdApiController extends ApiController class HouseholdApiController extends ApiController
{ {
public function __construct(private readonly EventDispatcherInterface $eventDispatcher, private readonly HouseholdRepository $householdRepository, private readonly HouseholdACLAwareRepositoryInterface $householdACLAwareRepository, private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry) {} public function __construct(private readonly EventDispatcherInterface $eventDispatcher, private readonly HouseholdRepository $householdRepository, private readonly HouseholdACLAwareRepositoryInterface $householdACLAwareRepository, protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
/** /**
* @return \Symfony\Component\HttpFoundation\JsonResponse * @return \Symfony\Component\HttpFoundation\JsonResponse

View File

@@ -13,10 +13,16 @@ namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class HouseholdCompositionTypeApiController extends ApiController class HouseholdCompositionTypeApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
/** /**
* @param QueryBuilder $query * @param QueryBuilder $query
*/ */

View File

@@ -23,6 +23,7 @@ use Chill\PersonBundle\Repository\Household\PositionRepository;
use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter; use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -33,9 +34,12 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Exception; use Symfony\Component\Serializer\Exception;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class HouseholdMemberController extends ApiController class HouseholdMemberController extends ApiController
{ {
private array $household_fields_visibility;
public function __construct( public function __construct(
private readonly UrlGeneratorInterface $generator, private readonly UrlGeneratorInterface $generator,
private readonly TranslatorInterface $translator, private readonly TranslatorInterface $translator,
@@ -44,8 +48,12 @@ class HouseholdMemberController extends ApiController
private readonly HouseholdRepository $householdRepository, private readonly HouseholdRepository $householdRepository,
private readonly Security $security, private readonly Security $security,
private readonly PositionRepository $positionRepository, private readonly PositionRepository $positionRepository,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, protected ParameterBagInterface $parameterBag,
) {} ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
$this->household_fields_visibility = $parameterBag->get('chill_person.household_fields');
}
#[Route(path: '/{_locale}/person/household/member/{id}/edit', name: 'chill_person_household_member_edit')] #[Route(path: '/{_locale}/person/household/member/{id}/edit', name: 'chill_person_household_member_edit')]
public function editMembership(Request $request, HouseholdMember $member): Response public function editMembership(Request $request, HouseholdMember $member): Response
@@ -144,6 +152,7 @@ class HouseholdMemberController extends ApiController
'allowHouseholdCreate' => $allowHouseholdCreate ?? true, 'allowHouseholdCreate' => $allowHouseholdCreate ?? true,
'allowHouseholdSearch' => $allowHouseholdSearch ?? true, 'allowHouseholdSearch' => $allowHouseholdSearch ?? true,
'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'), 'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'),
'displayDependents' => ('visible' == $this->household_fields_visibility['number_of_dependents']) ? true : false,
]; ];
// context // context

View File

@@ -12,10 +12,16 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller; namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class OpeningApiController extends ApiController class OpeningApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $qb): void protected function customizeQuery(string $action, Request $request, $qb): void
{ {
$qb->where($qb->expr()->gt('e.noActiveAfter', ':now')) $qb->where($qb->expr()->gt('e.noActiveAfter', ':now'))

View File

@@ -18,6 +18,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\PersonBundle\Security\AuthorizedCenterOnPersonCreationInterface; use Chill\PersonBundle\Security\AuthorizedCenterOnPersonCreationInterface;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -32,7 +33,9 @@ class PersonApiController extends ApiController
private readonly AuthorizedCenterOnPersonCreationInterface $authorizedCenterOnPersonCreation, private readonly AuthorizedCenterOnPersonCreationInterface $authorizedCenterOnPersonCreation,
private readonly ConfigPersonAltNamesHelper $configPersonAltNameHelper, private readonly ConfigPersonAltNamesHelper $configPersonAltNameHelper,
ParameterBagInterface $parameterBag, ParameterBagInterface $parameterBag,
ManagerRegistry $managerRegistry,
) { ) {
parent::__construct($managerRegistry);
$this->showCenters = $parameterBag->get('chill_main')['acl']['form_show_centers']; $this->showCenters = $parameterBag->get('chill_main')['acl']['form_show_centers'];
} }

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller; namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
class RelationApiController extends ApiController {} class RelationApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}

View File

@@ -15,12 +15,16 @@ use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\Relationships\RelationshipRepository; use Chill\PersonBundle\Repository\Relationships\RelationshipRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class RelationshipApiController extends ApiController class RelationshipApiController extends ApiController
{ {
public function __construct(private readonly RelationshipRepository $repository) {} public function __construct(private readonly RelationshipRepository $repository, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
/** /**
* @ParamConverter("person", options={"id": "person_id"}) * @ParamConverter("person", options={"id": "person_id"})

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Service\SocialWork\SocialActionCSVExportService;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\UnavailableStream;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
class SocialActionCSVExportController extends AbstractController
{
public function __construct(
private readonly SocialActionRepository $socialActionRepository,
private readonly Security $security,
private readonly SocialActionCSVExportService $socialActionCSVExportService,
) {}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
#[Route(path: '/{_locale}/admin/social-work/social-action/export/list.{_format}', name: 'chill_person_social_action_export_list', requirements: ['_format' => 'csv'])]
public function socialActionList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$actions = $this->socialActionRepository->findAllOrdered();
$csv = $this->socialActionCSVExportService->generateCsv($actions);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename=results.csv',
]
);
}
}

View File

@@ -13,10 +13,16 @@ namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class SocialIssueApiController extends ApiController class SocialIssueApiController extends ApiController
{ {
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void protected function customizeQuery(string $action, Request $request, $query): void
{ {
$query->where( $query->where(

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Service\SocialWork\SocialIssueCSVExportService;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\UnavailableStream;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
class SocialIssueCSVExportController extends AbstractController
{
public function __construct(
private readonly SocialIssueRepository $socialIssueRepository,
private readonly Security $security,
private readonly SocialIssueCSVExportService $socialIssueCSVExportService,
) {}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
#[Route(path: '/{_locale}/admin/social-work/social-issue/export/list.{_format}', name: 'chill_person_social_issue_export_list', requirements: ['_format' => 'csv'])]
public function socialIssueList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$socialIssues = $this->socialIssueRepository->findAllOrdered();
$csv = $this->socialIssueCSVExportService->generateCsv($socialIssues);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; users.csv',
]
);
}
}

View File

@@ -1,149 +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\PersonBundle\Controller;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\UnavailableStream;
use League\Csv\Writer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class SocialWorkExportController extends AbstractController
{
public function __construct(
private readonly SocialIssueRepository $socialIssueRepository,
private readonly SocialActionRepository $socialActionRepository,
private readonly Security $security,
private readonly TranslatorInterface $translator,
private readonly SocialIssueRender $socialIssueRender,
private readonly SocialActionRender $socialActionRender,
) {}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
#[Route(path: '/{_locale}/admin/social-work/social-issue/export/list.{_format}', name: 'chill_person_social_issue_export_list', requirements: ['_format' => 'csv'])]
public function socialIssueList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$socialIssues = $this->socialIssueRepository->findAll();
$socialIssues = array_map(fn ($issue) => [
'id' => $issue->getId(),
'title' => $this->socialIssueRender->renderString($issue, []),
'ordering' => $issue->getOrdering(),
'desactivationDate' => $issue->getDesactivationDate(),
], $socialIssues);
$csv = Writer::createFromPath('php://temp', 'r+');
$csv->insertOne(
array_map(
fn (string $e) => $this->translator->trans($e),
[
'Id',
'Title',
'Ordering',
'goal.desactivationDate',
]
)
);
$csv->addFormatter(fn (array $row) => null !== ($row['desactivationDate'] ?? null) ? array_merge($row, ['desactivationDate' => $row['desactivationDate']->format('Y-m-d')]) : $row);
$csv->insertAll($socialIssues);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; users.csv',
]
);
}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
#[Route(path: '/{_locale}/admin/social-work/social-action/export/list.{_format}', name: 'chill_person_social_action_export_list', requirements: ['_format' => 'csv'])]
public function socialActionList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$socialActions = $this->socialActionRepository->findAll();
$socialActions = array_map(fn ($action) => [
'id' => $action->getId(),
'title' => $this->socialActionRender->renderString($action, []),
'desactivationDate' => $action->getDesactivationDate(),
'socialIssue' => $this->socialIssueRender->renderString($action->getIssue(), []),
'ordering' => $action->getOrdering(),
], $socialActions);
$csv = Writer::createFromPath('php://temp', 'r+');
$csv->insertOne(
array_map(
fn (string $e) => $this->translator->trans($e),
[
'Id',
'Title',
'goal.desactivationDate',
'Social issue',
'Ordering',
]
)
);
$csv->addFormatter(fn (array $row) => null !== ($row['desactivationDate'] ?? null) ? array_merge($row, ['desactivationDate' => $row['desactivationDate']->format('Y-m-d')]) : $row);
$csv->insertAll($socialActions);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; users.csv',
]
);
}
}

View File

@@ -16,12 +16,16 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\GoalRepository; use Chill\PersonBundle\Repository\SocialWork\GoalRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class SocialWorkGoalApiController extends ApiController class SocialWorkGoalApiController extends ApiController
{ {
public function __construct(private readonly GoalRepository $goalRepository, private readonly PaginatorFactory $paginator) {} public function __construct(private readonly GoalRepository $goalRepository, private readonly PaginatorFactory $paginator, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function listBySocialAction(Request $request, SocialAction $action): Response public function listBySocialAction(Request $request, SocialAction $action): Response
{ {

View File

@@ -16,12 +16,16 @@ use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\Goal; use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\ResultRepository; use Chill\PersonBundle\Repository\SocialWork\ResultRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class SocialWorkResultApiController extends ApiController class SocialWorkResultApiController extends ApiController
{ {
public function __construct(private readonly ResultRepository $resultRepository) {} public function __construct(private readonly ResultRepository $resultRepository, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function listByGoal(Request $request, Goal $goal): Response public function listByGoal(Request $request, Goal $goal): Response
{ {

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -26,7 +27,10 @@ final class SocialWorkSocialActionApiController extends ApiController
private readonly SocialIssueRepository $socialIssueRepository, private readonly SocialIssueRepository $socialIssueRepository,
private readonly PaginatorFactory $paginator, private readonly PaginatorFactory $paginator,
private readonly ClockInterface $clock, private readonly ClockInterface $clock,
) {} ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
}
public function listBySocialIssueApi($id, Request $request) public function listBySocialIssueApi($id, Request $request)
{ {

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\PersonBundle\DataFixtures\ORM;
use Chill\PersonBundle\Entity\AdministrativeStatus;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
use Doctrine\Persistence\ObjectManager;
class LoadAdministrativeStatus extends Fixture implements FixtureGroupInterface
{
public static function getGroups(): array
{
return ['administrative_status'];
}
public function load(ObjectManager $manager): void
{
$status = [
['name' => ['fr' => 'situation administrative régulière']],
['name' => ['fr' => 'sans papier']],
['name' => ['fr' => 'séjour provisoire']],
];
foreach ($status as $val) {
$administrativeStatus = (new AdministrativeStatus())
->setName($val['name'])
->setActive(true);
$manager->persist($administrativeStatus);
}
$manager->flush();
}
}

View File

@@ -15,12 +15,15 @@ use Chill\MainBundle\DependencyInjection\MissingBundleException;
use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\PersonBundle\Controller\AccompanyingPeriodCommentApiController; use Chill\PersonBundle\Controller\AccompanyingPeriodCommentApiController;
use Chill\PersonBundle\Controller\AccompanyingPeriodResourceApiController; use Chill\PersonBundle\Controller\AccompanyingPeriodResourceApiController;
use Chill\PersonBundle\Controller\AdministrativeStatusController;
use Chill\PersonBundle\Controller\EmploymentStatusController; use Chill\PersonBundle\Controller\EmploymentStatusController;
use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController; use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController;
use Chill\PersonBundle\Controller\RelationApiController; use Chill\PersonBundle\Controller\RelationApiController;
use Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AdministrativeStatus;
use Chill\PersonBundle\Entity\EmploymentStatus; use Chill\PersonBundle\Entity\EmploymentStatus;
use Chill\PersonBundle\Form\AdministrativeStatusType;
use Chill\PersonBundle\Form\EmploymentStatusType; use Chill\PersonBundle\Form\EmploymentStatusType;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodCommentVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodCommentVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
@@ -57,6 +60,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$this->handlePersonFieldsParameters($container, $config['person_fields']); $this->handlePersonFieldsParameters($container, $config['person_fields']);
$this->handleAccompanyingPeriodsFieldsParameters($container, $config['accompanying_periods_fields']); $this->handleAccompanyingPeriodsFieldsParameters($container, $config['accompanying_periods_fields']);
$this->handleHouseholdFieldsParameters($container, $config['household_fields']);
$container->setParameter( $container->setParameter(
'chill_person.allow_multiple_simultaneous_accompanying_periods', 'chill_person.allow_multiple_simultaneous_accompanying_periods',
@@ -132,6 +136,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
'chill_accompanying_periods' => [ 'chill_accompanying_periods' => [
'fields' => $config['accompanying_periods_fields'], 'fields' => $config['accompanying_periods_fields'],
], ],
'chill_household' => [
'fields' => $config['household_fields'],
],
], ],
'form_themes' => ['@ChillPerson/Export/ListPersonFormFields.html.twig'], 'form_themes' => ['@ChillPerson/Export/ListPersonFormFields.html.twig'],
]; ];
@@ -195,6 +202,28 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
], ],
], ],
], ],
[
'class' => AdministrativeStatus::class,
'name' => 'administrative_status',
'base_path' => '/admin/administrative',
'base_role' => 'ROLE_ADMIN',
'form_class' => AdministrativeStatusType::class,
'controller' => AdministrativeStatusController::class,
'actions' => [
'index' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillPerson/AdministrativeStatus/index.html.twig',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillPerson/AdministrativeStatus/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillPerson/AdministrativeStatus/edit.html.twig',
],
],
],
[ [
'class' => EmploymentStatus::class, 'class' => EmploymentStatus::class,
'name' => 'employment_status', 'name' => 'employment_status',
@@ -1117,6 +1146,23 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
} }
} }
private function handleHouseholdFieldsParameters(ContainerBuilder $container, $config)
{
$container->setParameter('chill_person.household_fields', $config);
foreach ($config as $key => $value) {
switch ($key) {
case 'enabled':
break;
default:
$container->setParameter('chill_person.household_fields.'.$key, $value);
break;
}
}
}
private function handlePersonFieldsParameters(ContainerBuilder $container, $config) private function handlePersonFieldsParameters(ContainerBuilder $container, $config)
{ {
if (\array_key_exists('enabled', $config)) { if (\array_key_exists('enabled', $config)) {

View File

@@ -83,9 +83,11 @@ class Configuration implements ConfigurationInterface
->append($this->addFieldNode('accompanying_period')) ->append($this->addFieldNode('accompanying_period'))
->append($this->addFieldNode('memo')) ->append($this->addFieldNode('memo'))
->append($this->addFieldNode('number_of_children')) ->append($this->addFieldNode('number_of_children'))
->append($this->addFieldNode('number_of_dependents', 'hidden'))
->append($this->addFieldNode('acceptEmail')) ->append($this->addFieldNode('acceptEmail'))
->append($this->addFieldNode('deathdate')) ->append($this->addFieldNode('deathdate'))
->append($this->addFieldNode('employment_status', 'hidden')) ->append($this->addFieldNode('employment_status', 'hidden'))
->append($this->addFieldNode('administrative_status', 'hidden'))
->arrayNode('alt_names') ->arrayNode('alt_names')
->defaultValue([]) ->defaultValue([])
->arrayPrototype() ->arrayPrototype()
@@ -108,6 +110,12 @@ class Configuration implements ConfigurationInterface
->end() ->end()
->end() // children for 'person_fields', parent = array 'person_fields' ->end() // children for 'person_fields', parent = array 'person_fields'
->end() // person_fields, parent = children of root ->end() // person_fields, parent = children of root
->arrayNode('household_fields')
->canBeDisabled()
->children()
->append($this->addFieldNode('number_of_dependents', 'hidden'))
->end()
->end()
->arrayNode('accompanying_periods_fields') ->arrayNode('accompanying_periods_fields')
->canBeDisabled() ->canBeDisabled()
->children() ->children()

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Entity;
use Chill\PersonBundle\Repository\AdministrativeStatusRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['chill_person_administrative_status' => AdministrativeStatus::class])]
#[ORM\Entity(repositoryClass: AdministrativeStatusRepository::class)]
#[ORM\Table(name: 'chill_person_administrative_status')]
class AdministrativeStatus
{
#[Serializer\Groups(['read', 'docgen:read'])]
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
private ?int $id = null;
#[Serializer\Groups(['read', 'docgen:read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
#[Serializer\Context(['is-translatable' => true], groups: ['docgen:read'])]
private array $name = [];
#[Serializer\Groups(['read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
private bool $active = true;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::FLOAT, name: 'ordering', nullable: true, options: ['default' => '0.0'])]
private float $order = 0;
public function getId(): ?int
{
return $this->id;
}
public function getActive(): ?bool
{
return $this->active;
}
public function getName(): ?array
{
return $this->name;
}
public function getOrder(): ?float
{
return $this->order;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function setName(array $name): self
{
$this->name = $name;
return $this;
}
public function setOrder(float $order): self
{
$this->order = $order;
return $this;
}
}

View File

@@ -11,11 +11,12 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Entity; namespace Chill\PersonBundle\Entity;
use Chill\PersonBundle\Repository\EmploymentStatusRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Serializer\Annotation as Serializer;
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['chill_person_employment_status' => EmploymentStatus::class])] #[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['chill_person_employment_status' => EmploymentStatus::class])]
#[ORM\Entity] #[ORM\Entity(repositoryClass: EmploymentStatusRepository::class)]
#[ORM\Table(name: 'chill_person_employment_status')] #[ORM\Table(name: 'chill_person_employment_status')]
class EmploymentStatus class EmploymentStatus
{ {

View File

@@ -58,6 +58,18 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])]
private ?int $numberOfChildren = null; private ?int $numberOfChildren = null;
#[Assert\NotNull]
#[Assert\GreaterThanOrEqual(0, groups: ['Default', 'household_composition'])]
#[Serializer\Groups(['docgen:read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])]
private ?int $numberOfDependents = null;
#[Assert\NotNull]
#[Assert\GreaterThanOrEqual(0, groups: ['Default', 'household_composition'])]
#[Serializer\Groups(['docgen:read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])]
private ?int $numberOfDependentsWithDisabilities = null;
#[Assert\NotNull(groups: ['Default', 'household_composition'])] #[Assert\NotNull(groups: ['Default', 'household_composition'])]
#[Serializer\Groups(['docgen:read'])] #[Serializer\Groups(['docgen:read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: false)] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: false)]
@@ -98,6 +110,16 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
return $this->numberOfChildren; return $this->numberOfChildren;
} }
public function getNumberOfDependents(): ?int
{
return $this->numberOfDependents;
}
public function getNumberOfDependentsWithDisabilities(): ?int
{
return $this->numberOfDependentsWithDisabilities;
}
public function getStartDate(): ?\DateTimeImmutable public function getStartDate(): ?\DateTimeImmutable
{ {
return $this->startDate; return $this->startDate;
@@ -142,6 +164,20 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
return $this; return $this;
} }
public function setNumberOfDependents(?int $numberOfDependents): HouseholdComposition
{
$this->numberOfDependents = $numberOfDependents;
return $this;
}
public function setNumberOfDependentsWithDisabilities(?int $numberOfDependentsWithDisabilities): HouseholdComposition
{
$this->numberOfDependentsWithDisabilities = $numberOfDependentsWithDisabilities;
return $this;
}
public function setStartDate(?\DateTimeImmutable $startDate): HouseholdComposition public function setStartDate(?\DateTimeImmutable $startDate): HouseholdComposition
{ {
$this->startDate = $startDate; $this->startDate = $startDate;

View File

@@ -304,6 +304,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
private string $memo = ''; private string $memo = '';
/**
* The person's administrative status.
*/
#[ORM\ManyToOne(targetEntity: AdministrativeStatus::class)]
#[ORM\JoinColumn(nullable: true)]
private ?AdministrativeStatus $administrativeStatus = null;
/** /**
* The person's mobile phone number. * The person's mobile phone number.
*/ */
@@ -777,6 +784,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->addresses; return $this->addresses;
} }
public function getAdministrativeStatus(): ?AdministrativeStatus
{
return $this->administrativeStatus;
}
/** /**
* Return the age of a person, calculated at the date 'now'. * Return the age of a person, calculated at the date 'now'.
* *
@@ -1420,6 +1432,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this; return $this;
} }
public function setAdministrativeStatus(?AdministrativeStatus $administrativeStatus): self
{
$this->administrativeStatus = $administrativeStatus;
return $this;
}
public function setAcceptSMS(bool $acceptSMS): self public function setAcceptSMS(bool $acceptSMS): self
{ {
$this->acceptSMS = $acceptSMS; $this->acceptSMS = $acceptSMS;

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\PersonAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\AdministrativeStatusRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class AdministrativeStatusAggregator implements AggregatorInterface
{
public function __construct(private AdministrativeStatusRepository $administrativeStatusRepository, private TranslatableStringHelper $translatableStringHelper) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->leftJoin('person.administrativeStatus', 'admin_status');
$qb->addSelect('admin_status.id as administrative_status_aggregator');
$qb->addGroupBy('administrative_status_aggregator');
}
public function applyOn()
{
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Administrative status';
}
if (null === $value || '' === $value) {
return '';
}
$g = $this->administrativeStatusRepository->find($value);
return $this->translatableStringHelper->localize($g->getName());
};
}
public function getQueryKeys($data)
{
return ['administrative_status_aggregator'];
}
public function getTitle()
{
return 'Group people by administrative status';
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\PersonAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\EmploymentStatusRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class EmploymentStatusAggregator implements AggregatorInterface
{
public function __construct(private EmploymentStatusRepository $employmentStatusRepository, private TranslatableStringHelper $translatableStringHelper) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->leftJoin('person.employmentStatus', 'es');
$qb->addSelect('es.id as employment_status_aggregator');
$qb->addGroupBy('employment_status_aggregator');
}
public function applyOn()
{
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Employment status';
}
if (null === $value || '' === $value) {
return '';
}
$g = $this->employmentStatusRepository->find($value);
return $this->translatableStringHelper->localize($g->getName());
};
}
public function getQueryKeys($data)
{
return ['employment_status_aggregator'];
}
public function getTitle()
{
return 'Group people by employment status';
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Form;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Chill\PersonBundle\Entity\AdministrativeStatus;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AdministrativeStatusType extends \Symfony\Component\Form\AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TranslatableStringFormType::class, [
'required' => true,
])
->add('active', ChoiceType::class, [
'choices' => [
'Active' => true,
'Inactive' => false,
],
])
->add('order', NumberType::class);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => AdministrativeStatus::class,
]);
}
}

View File

@@ -19,10 +19,19 @@ use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class HouseholdCompositionType extends AbstractType class HouseholdCompositionType extends AbstractType
{ {
public function __construct(private readonly HouseholdCompositionTypeRepository $householdCompositionTypeRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper) {} private array $household_fields_visibility;
public function __construct(
private readonly HouseholdCompositionTypeRepository $householdCompositionTypeRepository,
private readonly TranslatableStringHelperInterface $translatableStringHelper,
protected ParameterBagInterface $parameterBag,
) {
$this->household_fields_visibility = $parameterBag->get('chill_person.household_fields');
}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
@@ -42,7 +51,19 @@ class HouseholdCompositionType extends AbstractType
->add('numberOfChildren', IntegerType::class, [ ->add('numberOfChildren', IntegerType::class, [
'required' => true, 'required' => true,
'label' => 'household_composition.numberOfChildren', 'label' => 'household_composition.numberOfChildren',
]);
if ('visible' == $this->household_fields_visibility['number_of_dependents']) {
$builder
->add('numberOfDependents', IntegerType::class, [
'required' => true,
'label' => 'household_composition.numberOfDependents',
]) ])
->add('numberOfDependentsWithDisabilities', IntegerType::class, [
'required' => true,
'label' => 'household_composition.numberOfDependentsWithDisabilities',
]);
}
$builder
->add('comment', CommentType::class, [ ->add('comment', CommentType::class, [
'required' => false, 'required' => false,
]); ]);

View File

@@ -26,6 +26,7 @@ use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Entity\PersonPhone;
use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Form\Type\PersonAltNameType;
use Chill\PersonBundle\Form\Type\PersonPhoneType; use Chill\PersonBundle\Form\Type\PersonPhoneType;
use Chill\PersonBundle\Form\Type\PickAdministrativeStatusType;
use Chill\PersonBundle\Form\Type\PickEmploymentStatusType; use Chill\PersonBundle\Form\Type\PickEmploymentStatusType;
use Chill\PersonBundle\Form\Type\PickGenderType; use Chill\PersonBundle\Form\Type\PickGenderType;
use Chill\PersonBundle\Form\Type\Select2MaritalStatusType; use Chill\PersonBundle\Form\Type\Select2MaritalStatusType;
@@ -108,6 +109,11 @@ class PersonType extends AbstractType
->add('employmentStatus', PickEmploymentStatusType::class, ['required' => false]); ->add('employmentStatus', PickEmploymentStatusType::class, ['required' => false]);
} }
if ('visible' === $this->config['administrative_status']) {
$builder
->add('administrativeStatus', PickAdministrativeStatusType::class, ['required' => false]);
}
if ('visible' === $this->config['place_of_birth']) { if ('visible' === $this->config['place_of_birth']) {
$builder->add('placeOfBirth', TextType::class, [ $builder->add('placeOfBirth', TextType::class, [
'required' => false, 'required' => false,

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Form\Type;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AdministrativeStatus;
class PickAdministrativeStatusType extends AbstractType
{
public function __construct(
private readonly TranslatableStringHelperInterface $translatableStringHelper,
) {}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('label', 'Administrative Status')
->setDefault(
'choice_label',
fn (AdministrativeStatus $administrativeStatus): string => $this->translatableStringHelper->localize($administrativeStatus->getName())
)
->setDefault(
'query_builder',
static fn (EntityRepository $er): QueryBuilder => $er->createQueryBuilder('c')
->where('c.active = true')
->orderBy('c.order'),
)
->setDefault('placeholder', $this->translatableStringHelper->localize(['Select an option…']))
->setDefault('class', AdministrativeStatus::class);
}
public function getParent()
{
return EntityType::class;
}
}

View File

@@ -65,6 +65,12 @@ class AdminPersonMenuBuilder implements LocalMenuBuilderInterface
])->setExtras(['order' => 2035]); ])->setExtras(['order' => 2035]);
} }
if ('visible' == $this->fields_visibility['administrative_status']) {
$menu->addChild('Administrative status', [
'route' => 'chill_crud_administrative_status_index',
])->setExtras(['order' => 2036]);
}
$menu->addChild('person_admin.person_resource_kind', [ $menu->addChild('person_admin.person_resource_kind', [
'route' => 'chill_crud_person_resource-kind_index', 'route' => 'chill_crud_person_resource-kind_index',
])->setExtras(['order' => 2040]); ])->setExtras(['order' => 2040]);

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Repository;
use Chill\PersonBundle\Entity\AdministrativeStatus;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class AdministrativeStatusRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, AdministrativeStatus::class);
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Repository;
use Chill\PersonBundle\Entity\EmploymentStatus;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class EmploymentStatusRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, EmploymentStatus::class);
}
}

View File

@@ -44,6 +44,14 @@ final readonly class SocialActionRepository implements ObjectRepository
return $this->repository->findAll(); return $this->repository->findAll();
} }
public function findAllOrdered(): array
{
return $this->repository->createQueryBuilder('si')
->orderBy('si.ordering', 'ASC')
->getQuery()
->getResult();
}
/** /**
* @return array|SocialAction[] * @return array|SocialAction[]
*/ */

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