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
assets/*
!assets/translator.ts
migrations/*
templates/*
translations/*

View File

@@ -113,7 +113,7 @@ lint:
- export PATH="./node_modules/.bin:$PATH"
script:
- yarn install --ignore-optional
- npx eslint-baseline "**/*.{js,vue}"
- npx eslint-baseline "src/**/*.{js,ts,vue}"
cache:
paths:
- 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).
## 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
### Fixed
* 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/translation": "^5.4",
"symfony/twig-bundle": "^5.4",
"symfony/ux-translator": "^2.22",
"symfony/validator": "^5.4",
"symfony/webpack-encore-bundle": "^1.11",
"symfony/workflow": "^5.4",

View File

@@ -36,4 +36,5 @@ return [
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
Chill\WopiBundle\ChillWopiBundle::class => ['all' => 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
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
# 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 v3.0.0-RC3 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev
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
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).
@@ -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):
.. code-block:: txt
.. code-block::
[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.
@@ -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
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
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`. Then,
The required variables are:
- 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:
- 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:
.. code-block:: env
.. code-block:: bash
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
# 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,
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.
Prepare migrations and other tools
----------------------------------
Prepare database, messenger queue, and other configuration
----------------------------------------------------------
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
# prepare some 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, ...)
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
transport to redis, by adding this in the :code:`.env.local` or :code:`.env` file:
If you encounter this error:
.. 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
-----------------------------

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>`_).
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
# 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)
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)
bin/console chill:main:address-ref-from-best-addresse full
bin/console chill:main:address-ref-from-best-addresse fr full
.. note::

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@ const store = createStore({
state: {
me: null,
activity: window.activity,
accompanyingPeriodWorks: [],
socialIssuesOther: [],
socialActionsList: [],
availableLocations: [],
@@ -41,7 +42,7 @@ const store = createStore({
const allEntities = [
...store.getters.suggestedPersons,
...store.getters.suggestedRequestor,
...store.getters.suggestedUser,
...store.getters.suggestedUsers,
...store.getters.suggestedResources,
];
const uniqueIds = [
@@ -80,8 +81,7 @@ const store = createStore({
state.activity.activityType.thirdPartiesVisible !== 0),
);
},
suggestedUser(state) {
// console.log('current user', state.me)
suggestedUsers(state) {
const existingUserIds = state.activity.users.map((p) => p.id);
let suggestedUsers =
state.activity.activityType.usersVisible === 0
@@ -90,11 +90,18 @@ const store = createStore({
(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
if (state.me && !existingUserIds.includes(state.me.id)) {
suggestedUsers.push(state.me);
}
console.log("suggested users", suggestedUsers);
// console.log("suggested users", suggestedUsers);
return suggestedUsers;
},
@@ -223,6 +230,9 @@ const store = createStore({
addAvailableLocationGroup(state, group) {
state.availableLocations.push(group);
},
setAccompanyingPeriodWorks(state, works) {
state.accompanyingPeriodWorks = works;
},
},
actions: {
addIssueSelected({ commit }, issue) {
@@ -341,6 +351,17 @@ const store = createStore({
}
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 }) {
const url = `/api/1.0/main/whoami.json`;
makeFetch("GET", url).then((user) => {
@@ -353,5 +374,6 @@ const store = createStore({
store.dispatch("getWhoAmI");
prepareLocations(store);
store.dispatch("fetchAccompanyingPeriodWorks");
export default store;

View File

@@ -143,7 +143,10 @@ class ListActivitiesByAccompanyingPeriodContext implements
array_filter(
$works,
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);
}

View File

@@ -102,6 +102,32 @@ activity:
Remove a document: Supprimer le document
comment: Commentaire
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
# 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\Entity\User;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
class CalendarAPIController extends ApiController
{
public function __construct(private readonly CalendarRepository $calendarRepository) {}
public function __construct(private readonly CalendarRepository $calendarRepository, 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'])]
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\Entity\User;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
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'])]
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\MainBundle\CRUD\Controller\ApiController;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
@@ -27,7 +28,10 @@ class StoredObjectApiController extends ApiController
private readonly Security $security,
private readonly SerializerInterface $serializer,
private readonly EntityManagerInterface $entityManager,
) {}
ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
}
/**
* Creates a new stored object.

View File

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

View File

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

View File

@@ -39,6 +39,8 @@ abstract class AbstractCRUDController extends AbstractController
*/
protected array $crudConfig = [];
public function __construct(protected ManagerRegistry $managerRegistry) {}
/**
* get the role given from the config.
*/
@@ -213,7 +215,7 @@ abstract class AbstractCRUDController extends AbstractController
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\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -23,6 +24,11 @@ use Symfony\Component\Validator\ConstraintViolationListInterface;
class ApiController extends AbstractCRUDController
{
public function __construct(ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
/**
* 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\Entity\Address;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
@@ -20,7 +21,10 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
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.

View File

@@ -17,6 +17,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Repository\AddressReferenceRepository;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -26,7 +27,10 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
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')]
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\Pagination\PaginatorInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class CivilityApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
return $query->addOrderBy('e.order', 'ASC');

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
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\Pagination\PaginatorInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class GenderApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
$query

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
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\Pagination\PaginatorInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
/**
@@ -21,6 +22,11 @@ use Symfony\Component\HttpFoundation\Request;
*/
class LocationApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
$query

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
/**
@@ -19,6 +20,11 @@ use Symfony\Component\HttpFoundation\Request;
*/
class LocationTypeApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function customizeQuery(string $action, Request $request, $query): void
{
$query->andWhere(

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\PostalCodeRepositoryInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -26,7 +27,10 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
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')]
public function search(Request $request): JsonResponse

View File

@@ -12,10 +12,16 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class ScopeApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
if ('_index' === $action) {

View File

@@ -15,6 +15,7 @@ use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Security\ChillSecurity;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -22,7 +23,10 @@ use Symfony\Component\Routing\Annotation\Route;
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'])]
public function currentLocation(mixed $_format): JsonResponse

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
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;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class UserJobApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
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\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class ExportType extends AbstractType
{
@@ -29,7 +30,15 @@ class ExportType extends AbstractType
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)
{
@@ -77,6 +86,17 @@ class ExportType extends AbstractType
);
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, [
'aggregator_alias' => $alias,
'export_manager' => $this->exportManager,

View File

@@ -1,30 +1,32 @@
import ClassicEditorBase from "@ckeditor/ckeditor5-editor-classic/src/classiceditor";
import EssentialsPlugin from "@ckeditor/ckeditor5-essentials/src/essentials";
import MarkdownPlugin from "@ckeditor/ckeditor5-markdown-gfm/src/markdown";
import BoldPlugin from "@ckeditor/ckeditor5-basic-styles/src/bold";
import ItalicPlugin from "@ckeditor/ckeditor5-basic-styles/src/italic";
import BlockQuotePlugin from "@ckeditor/ckeditor5-block-quote/src/blockquote";
import HeadingPlugin from "@ckeditor/ckeditor5-heading/src/heading";
import LinkPlugin from "@ckeditor/ckeditor5-link/src/link";
import ListPlugin from "@ckeditor/ckeditor5-list/src/list";
import ParagraphPlugin from "@ckeditor/ckeditor5-paragraph/src/paragraph";
import {
Essentials,
Bold,
Italic,
Paragraph,
Markdown,
BlockQuote,
Heading,
Link,
List,
} from "ckeditor5";
import coreTranslations from "ckeditor5/translations/fr.js";
import "ckeditor5/ckeditor5.css";
import "./index.scss";
export default class ClassicEditor extends ClassicEditorBase {}
ClassicEditor.builtinPlugins = [
EssentialsPlugin,
MarkdownPlugin,
BoldPlugin,
ItalicPlugin,
BlockQuotePlugin,
HeadingPlugin,
LinkPlugin,
ListPlugin,
ParagraphPlugin,
];
ClassicEditor.defaultConfig = {
export default {
plugins: [
Essentials,
Markdown,
Bold,
Italic,
BlockQuote,
Heading,
Link,
List,
Paragraph,
],
toolbar: {
items: [
"heading",
@@ -39,5 +41,6 @@ ClassicEditor.defaultConfig = {
"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> =
document.querySelectorAll("textarea[ckeditor]");
ckeditorFields.forEach((field: HTMLTextAreaElement): void => {
ClassicEditor.create(field)
.then((editor) => {
//console.log( 'CkEditor was initialized', editor );
})
.catch((error) => {
console.error(error.stack);
});
ClassicEditor.create(field, config).catch((error) => {
console.error(error.stack);
throw error;
});
});
//Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea'));

View File

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

View File

@@ -12,6 +12,10 @@ function loadDynamicPicker(element) {
let apps = element.querySelectorAll('[data-module="pick-dynamic"]');
apps.forEach(function (el) {
let suggested;
let as_id;
let submit_on_adding_new_entity;
let label;
const isMultiple = parseInt(el.dataset.multiple) === 1,
uniqId = el.dataset.uniqid,
input = element.querySelector(
@@ -23,10 +27,10 @@ function loadDynamicPicker(element) {
: input.value === "[]" || input.value === ""
? null
: [JSON.parse(input.value)];
(suggested = JSON.parse(el.dataset.suggested)),
(as_id = parseInt(el.dataset.asId) === 1),
(submit_on_adding_new_entity =
parseInt(el.dataset.submitOnAddingNewEntity) === 1);
suggested = JSON.parse(el.dataset.suggested);
as_id = parseInt(el.dataset.asId) === 1;
submit_on_adding_new_entity =
parseInt(el.dataset.submitOnAddingNewEntity) === 1;
label = el.dataset.label;
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);
});

View File

@@ -232,19 +232,8 @@ const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
</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>
</form>
</div>

View File

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

View File

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

View File

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

View File

@@ -6,8 +6,8 @@
v-if="!subscriberFinal"
@click="subscribeTo('subscribe', 'final')"
>
<i class="fa fa-check fa-fw" />
{{ $t("subscribe_final") }}
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}
</button>
<button
class="btn btn-misc"
@@ -15,8 +15,8 @@
v-if="subscriberFinal"
@click="subscribeTo('unsubscribe', 'final')"
>
<i class="fa fa-times fa-fw" />
{{ $t("unsubscribe_final") }}
<i class="fa fa-times fa-fw"></i>
{{ trans(WORKFLOW_UNSUBSCRIBE_FINAL) }}
</button>
<button
class="btn btn-misc"
@@ -24,8 +24,8 @@
v-if="!subscriberStep"
@click="subscribeTo('subscribe', 'step')"
>
<i class="fa fa-check fa-fw" />
{{ $t("subscribe_all_steps") }}
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}
</button>
<button
class="btn btn-misc"
@@ -33,94 +33,55 @@
v-if="subscriberStep"
@click="subscribeTo('unsubscribe', 'step')"
>
<i class="fa fa-times fa-fw" />
{{ $t("unsubscribe_all_steps") }}
<i class="fa fa-times fa-fw"></i>
{{ trans(WORKFLOW_UNSUBSCRIBE_ALL_STEPS) }}
</button>
</div>
</template>
<script>
<script setup>
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 {
name: "EntityWorkflowVueSubscriber",
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
const props = defineProps({
entityWorkflowId: {
type: Number,
required: true,
},
props: {
entityWorkflowId: {
type: Number,
required: true,
},
subscriberStep: {
type: Boolean,
required: true,
},
subscriberFinal: {
type: Boolean,
required: true,
},
subscriberStep: {
type: Boolean,
required: true,
},
emits: ["subscriptionUpdated"],
methods: {
subscribeTo(step, to) {
let params = new URLSearchParams();
params.set("subscribe", to);
subscriberFinal: {
type: Boolean,
required: true,
},
});
const url =
`/api/1.0/main/workflow/${this.entityWorkflowId}/${step}?` +
params.toString();
//methods
const subscribeTo = (step, to) => {
let params = new URLSearchParams();
params.set("subscribe", to);
makeFetch("POST", url).then((response) => {
this.$emit("subscriptionUpdated", response);
});
},
},
const url =
`/api/1.0/main/workflow/${props.entityWorkflowId}/${step}?` +
params.toString();
makeFetch("POST", url).then((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">
<label class="list-group-item">
<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>
*/
// emit
const emit = defineEmits(["subscriptionUpdated"]);
</script>
<style scoped></style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex-table workflow" id="workflow-list">
<div
v-for="(w, i) in workflows"
v-for="(w, i) in props.workflows"
:key="`workflow-${i}`"
class="item-bloc"
>
@@ -48,7 +48,7 @@
<span
v-if="w.isOnHoldAtCurrentStep"
class="badge bg-success rounded-pill"
>{{ $t("on_hold") }}</span
>{{ trans(WORKFLOW_ON_HOLD) }}</span
>
</div>
@@ -56,11 +56,11 @@
<div class="item-col flex-grow-1">
<p v-if="isUserSubscribedToStep(w)">
<i class="fa fa-check fa-fw"></i>
{{ $t("you_subscribed_to_all_steps") }}
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS) }}
</p>
<p v-if="isUserSubscribedToFinal(w)">
<i class="fa fa-check fa-fw"></i>
{{ $t("you_subscribed_to_final_step") }}
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP) }}
</p>
</div>
<div class="item-col">
@@ -69,7 +69,7 @@
<a
:href="goToUrl(w)"
class="btn btn-sm btn-show"
:title="$t('action.show')"
:title="trans(SEE)"
></a>
</li>
</ul>
@@ -79,85 +79,65 @@
</div>
</template>
<script>
<script setup>
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 = {
messages: {
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",
},
// props
const props = defineProps({
workflows: {
type: Array,
required: true,
},
});
// methods
const goToUrl = (w) => `/fr/main/workflow/${w.id}/show`;
const getPopTitle = (step) => {
if (step.transitionPrevious != null) {
//console.log(step.transitionPrevious.text);
let freezed = step.isFreezed
? `<i class="fa fa-snowflake-o fa-sm me-1"></i>`
: ``;
return `${freezed}${step.transitionPrevious.text}`;
}
};
const getPopContent = (step) => {
if (step.transitionPrevious != null) {
if (step.transitionPreviousBy !== null) {
return `<ul class="small_in_title">
<li><span class="item-key">${trans(BY_USER)} : </span><b>${step.transitionPreviousBy.text}</b></li>
<li><span class="item-key">${trans(WORKFLOW_AT)} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
} else {
return `<ul class="small_in_title">
<li><span class="item-key">${trans(WORKFLOW_AT)} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
}
}
};
const formatDate = (datetime) =>
datetime.split("T")[0] + " " + datetime.split("T")[1].substring(0, 5);
const isUserSubscribedToStep = () => false;
const isUserSubscribedToFinal = () => false;
export default {
name: "ListWorkflow",
i18n: i18n,
props: {
workflows: {
type: Array,
required: true,
},
},
methods: {
goToUrl(w) {
return `/fr/main/workflow/${w.id}/show`;
},
getPopTitle(step) {
if (step.transitionPrevious != null) {
//console.log(step.transitionPrevious.text);
let freezed = step.isFreezed
? `<i class="fa fa-snowflake-o fa-sm me-1"></i>`
: ``;
return `${freezed}${step.transitionPrevious.text}`;
}
},
getPopContent(step) {
if (step.transitionPrevious != null) {
if (step.transitionPreviousBy !== null) {
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">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
} else {
return `<ul class="small_in_title">
<li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
}
}
},
formatDate(datetime) {
return (
datetime.split("T")[0] +
" " +
datetime.split("T")[1].substring(0, 5)
);
},
isUserSubscribedToStep(w) {
// todo
return false;
},
isUserSubscribedToFinal(w) {
// todo
return false;
},
},
mounted() {
const triggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="popover"]'),
);
const popoverList = triggerList.map(function (el) {
//console.log('popover', el)
return new Popover(el, {
html: true,
});
onMounted(() => {
const triggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="popover"]'),
);
triggerList.map(function (el) {
return new Popover(el, {
html: true,
});
},
};
});
});
</script>

View File

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

View File

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

View File

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

View File

@@ -8,10 +8,10 @@
]"
@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">
{{ $t("online_edit_document") }}
{{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }}
</span>
</a>
@@ -19,8 +19,8 @@
<div class="wopi-frame" v-if="isOpenDocument">
<modal
v-if="modal.showModal"
:modal-dialog-class="modal.modalDialogClass"
:hide-footer="true"
:modalDialogClass="modal.modalDialogClass"
:hideFooter="true"
@close="modal.showModal = false"
>
<template #header>
@@ -28,203 +28,164 @@
<span class="ms-auto me-3">
<span v-if="options.title">{{ options.title }}</span>
</span>
<!--
<a class="btn btn-outline-light">
<i class="fa fa-save fa-fw"></i>
{{ $t('save_and_quit') }}
</a>
-->
</template>
<template #body>
<div v-if="loading" class="loading">
<i
class="fa fa-circle-o-notch fa-spin fa-3x"
:title="$t('loading')"
/>
:title="trans(WOPI_LOADING)"
></i>
</div>
<iframe :src="this.wopiUrl" @load="loaded" />
<iframe :src="this.wopiUrl" @load="loaded"></iframe>
</template>
</modal>
</div>
<div v-else>
<modal
<Modal
v-if="modal.showModal"
modal-dialog-class="modal-sm"
modalDialogClass="modal-sm"
@close="modal.showModal = false"
>
<template #header>
<h3>{{ $t("invalid_title") }}</h3>
<template v-slot:header>
<h3>{{ trans(WOPI_INVALID_TITLE) }}</h3>
</template>
<template #body>
<template v-slot:body>
<div class="alert alert-warning">
{{ $t("invalid_message") }}
{{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }}
</div>
</template>
</modal>
</Modal>
</div>
</teleport>
</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 logo from "ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png";
export default {
name: "OpenWopiLink",
components: {
Modal,
// Props
const props = defineProps({
wopiUrl: {
type: String,
required: true,
},
props: {
wopiUrl: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
options: {
type: Object,
required: false,
},
type: {
type: String,
required: true,
},
data() {
return {
modal: {
showModal: false,
modalDialogClass: "modal-fullscreen", //modal-dialog-scrollable
},
logo: logo,
loading: false,
mime: [
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
"application/clarisworks",
"application/coreldraw",
"application/macwriteii",
"application/msword",
"application/pdf",
"application/vnd.lotus-1-2-3",
"application/vnd.ms-excel",
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
"application/vnd.ms-excel.sheet.macroEnabled.12",
"application/vnd.ms-excel.template.macroEnabled.12",
"application/vnd.ms-powerpoint",
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
"application/vnd.ms-powerpoint.template.macroEnabled.12",
"application/vnd.ms-visio.drawing",
"application/vnd.ms-word.document.macroEnabled.12",
"application/vnd.ms-word.template.macroEnabled.12",
"application/vnd.ms-works",
"application/vnd.oasis.opendocument.chart",
"application/vnd.oasis.opendocument.formula",
"application/vnd.oasis.opendocument.graphics",
"application/vnd.oasis.opendocument.graphics-flat-xml",
"application/vnd.oasis.opendocument.graphics-template",
"application/vnd.oasis.opendocument.presentation",
"application/vnd.oasis.opendocument.presentation-flat-xml",
"application/vnd.oasis.opendocument.presentation-template",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.spreadsheet-flat-xml",
"application/vnd.oasis.opendocument.spreadsheet-template",
"application/vnd.oasis.opendocument.text",
"application/vnd.oasis.opendocument.text-flat-xml",
"application/vnd.oasis.opendocument.text-master",
"application/vnd.oasis.opendocument.text-master-template",
"application/vnd.oasis.opendocument.text-template",
"application/vnd.oasis.opendocument.text-web",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
"application/vnd.openxmlformats-officedocument.presentationml.template",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
"application/vnd.sun.xml.calc",
"application/vnd.sun.xml.calc.template",
"application/vnd.sun.xml.chart",
"application/vnd.sun.xml.draw",
"application/vnd.sun.xml.draw.template",
"application/vnd.sun.xml.impress",
"application/vnd.sun.xml.impress.template",
"application/vnd.sun.xml.math",
"application/vnd.sun.xml.writer",
"application/vnd.sun.xml.writer.global",
"application/vnd.sun.xml.writer.template",
"application/vnd.visio",
"application/vnd.visio2013",
"application/vnd.wordperfect",
"application/x-abiword",
"application/x-aportisdoc",
"application/x-dbase",
"application/x-dif-document",
"application/x-fictionbook+xml",
"application/x-gnumeric",
"application/x-hwp",
"application/x-iwork-keynote-sffkey",
"application/x-iwork-numbers-sffnumbers",
"application/x-iwork-pages-sffpages",
"application/x-mspublisher",
"application/x-mswrite",
"application/x-pagemaker",
"application/x-sony-bbeb",
"application/x-t602",
],
};
},
computed: {
isOpenDocument() {
if (this.mime.indexOf(this.type) !== -1) {
return true;
}
return false;
},
noText() {
if (typeof this.options.noText !== "undefined") {
return this.options.noText === true;
}
return false;
},
isChangeIcon() {
if (typeof this.options.changeIcon !== "undefined") {
return !(
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.",
},
},
options: {
type: Object,
required: false,
},
});
// data
const modal = ref({
showModal: false,
modalDialogClass: "modal-fullscreen",
});
const loading = ref(false);
// MIME types
const mime = [
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
"application/clarisworks",
"application/coreldraw",
"application/macwriteii",
"application/msword",
"application/pdf",
"application/vnd.lotus-1-2-3",
"application/vnd.ms-excel",
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
"application/vnd.ms-excel.sheet.macroEnabled.12",
"application/vnd.ms-excel.template.macroEnabled.12",
"application/vnd.ms-powerpoint",
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
"application/vnd.ms-powerpoint.template.macroEnabled.12",
"application/vnd.ms-visio.drawing",
"application/vnd.ms-word.document.macroEnabled.12",
"application/vnd.ms-word.template.macroEnabled.12",
"application/vnd.ms-works",
"application/vnd.oasis.opendocument.chart",
"application/vnd.oasis.opendocument.formula",
"application/vnd.oasis.opendocument.graphics",
"application/vnd.oasis.opendocument.graphics-flat-xml",
"application/vnd.oasis.opendocument.graphics-template",
"application/vnd.oasis.opendocument.presentation",
"application/vnd.oasis.opendocument.presentation-flat-xml",
"application/vnd.oasis.opendocument.presentation-template",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.spreadsheet-flat-xml",
"application/vnd.oasis.opendocument.spreadsheet-template",
"application/vnd.oasis.opendocument.text",
"application/vnd.oasis.opendocument.text-flat-xml",
"application/vnd.oasis.opendocument.text-master",
"application/vnd.oasis.opendocument.text-master-template",
"application/vnd.oasis.opendocument.text-template",
"application/vnd.oasis.opendocument.text-web",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
"application/vnd.openxmlformats-officedocument.presentationml.template",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
"application/vnd.sun.xml.calc",
"application/vnd.sun.xml.calc.template",
"application/vnd.sun.xml.chart",
"application/vnd.sun.xml.draw",
"application/vnd.sun.xml.draw.template",
"application/vnd.sun.xml.impress",
"application/vnd.sun.xml.impress.template",
"application/vnd.sun.xml.math",
"application/vnd.sun.xml.writer",
"application/vnd.sun.xml.writer.global",
"application/vnd.sun.xml.writer.template",
"application/vnd.visio",
"application/vnd.visio2013",
"application/vnd.wordperfect",
"application/x-abiword",
"application/x-aportisdoc",
"application/x-dbase",
"application/x-dif-document",
"application/x-fictionbook+xml",
"application/x-gnumeric",
"application/x-hwp",
"application/x-iwork-keynote-sffkey",
"application/x-iwork-numbers-sffnumbers",
"application/x-iwork-pages-sffpages",
"application/x-mspublisher",
"application/x-mswrite",
"application/x-pagemaker",
"application/x-sony-bbeb",
"application/x-t602",
];
// Computed
const isOpenDocument = computed(() => mime.includes(props.type));
const noText = computed(() => props.options?.noText === true);
const isChangeIcon = computed(() => !!props.options?.changeIcon);
const isChangeClass = computed(() => !!props.options?.changeClass);
// Methods
const openModal = () => {
loading.value = true;
modal.value.showModal = true;
};
const loaded = () => {
loading.value = false;
};
</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
module.exports = function (encore, entries) {
@@ -79,8 +37,6 @@ module.exports = function (encore, entries) {
__dirname + "/Resources/public/page/export/download-export.js",
);
buildCKEditor(encore);
// Modules entrypoints
encore.addEntry(
"mod_collection",

View File

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

View File

@@ -44,6 +44,9 @@ address_fields: Données liées à l'adresse
Datas: Données
No title: Aucun titre
icon: icône
See: Voir
Name: Nom
Label: Nom
user:
profile:
@@ -124,6 +127,49 @@ address:
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
real address: Adresse d'un domicile
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:
floor: ét
corridor: coul
@@ -508,6 +554,8 @@ Follow workflow: Suivre la décision
Workflow history: Historique de la décision
workflow:
list: Liste des workflows associés
associated: workflow associé
deleted: Workflow supprimé
Created by: Créé par
My decision: Ma décision
@@ -553,6 +601,7 @@ workflow:
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
For: Pour
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.
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
@@ -575,6 +624,12 @@ workflow:
public_views_by_ip: Visualisation par adresse IP
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:
expired_link_title: Lien expiré
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
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:
address_helper:
id: Identifiant de l'adresse
@@ -676,6 +735,25 @@ export:
steps: Escaliers
_lat: Latitude
_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:
year_previous_start: Début de l'année précédente
@@ -795,4 +873,43 @@ gender:
Select gender translation: Traduction grammaticale
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\SocialWork\SocialIssue;
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -46,7 +47,10 @@ use Symfony\Component\Workflow\Registry;
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
{
@@ -305,6 +309,20 @@ final class AccompanyingCourseApiController extends ApiController
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
{
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\Counter;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -27,7 +28,10 @@ class AccompanyingCourseWorkApiController extends ApiController
public function __construct(
private readonly AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository,
private readonly Security $security,
) {}
ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
}
#[Route(path: '/api/1.0/person/accompanying-period/work/my-near-end')]
public function myWorksNearEndDate(Request $request): JsonResponse

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
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;
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\HouseholdRepository;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -31,7 +32,10 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
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

View File

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

View File

@@ -23,6 +23,7 @@ use Chill\PersonBundle\Repository\Household\PositionRepository;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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\Serializer\Exception;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class HouseholdMemberController extends ApiController
{
private array $household_fields_visibility;
public function __construct(
private readonly UrlGeneratorInterface $generator,
private readonly TranslatorInterface $translator,
@@ -44,8 +48,12 @@ class HouseholdMemberController extends ApiController
private readonly HouseholdRepository $householdRepository,
private readonly Security $security,
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')]
public function editMembership(Request $request, HouseholdMember $member): Response
@@ -144,6 +152,7 @@ class HouseholdMemberController extends ApiController
'allowHouseholdCreate' => $allowHouseholdCreate ?? true,
'allowHouseholdSearch' => $allowHouseholdSearch ?? true,
'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'),
'displayDependents' => ('visible' == $this->household_fields_visibility['number_of_dependents']) ? true : false,
];
// context

View File

@@ -12,10 +12,16 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class OpeningApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $qb): void
{
$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\Security\Authorization\PersonVoter;
use Chill\PersonBundle\Security\AuthorizedCenterOnPersonCreationInterface;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -32,7 +33,9 @@ class PersonApiController extends ApiController
private readonly AuthorizedCenterOnPersonCreationInterface $authorizedCenterOnPersonCreation,
private readonly ConfigPersonAltNamesHelper $configPersonAltNameHelper,
ParameterBagInterface $parameterBag,
ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
$this->showCenters = $parameterBag->get('chill_main')['acl']['form_show_centers'];
}

View File

@@ -12,5 +12,12 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
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\Repository\Relationships\RelationshipRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Response;
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"})

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\Pagination\PaginatorInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class SocialIssueApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
$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\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\GoalRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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
{

View File

@@ -16,12 +16,16 @@ use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\ResultRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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
{

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -26,7 +27,10 @@ final class SocialWorkSocialActionApiController extends ApiController
private readonly SocialIssueRepository $socialIssueRepository,
private readonly PaginatorFactory $paginator,
private readonly ClockInterface $clock,
) {}
ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
}
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\PersonBundle\Controller\AccompanyingPeriodCommentApiController;
use Chill\PersonBundle\Controller\AccompanyingPeriodResourceApiController;
use Chill\PersonBundle\Controller\AdministrativeStatusController;
use Chill\PersonBundle\Controller\EmploymentStatusController;
use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController;
use Chill\PersonBundle\Controller\RelationApiController;
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AdministrativeStatus;
use Chill\PersonBundle\Entity\EmploymentStatus;
use Chill\PersonBundle\Form\AdministrativeStatusType;
use Chill\PersonBundle\Form\EmploymentStatusType;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodCommentVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
@@ -57,6 +60,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$this->handlePersonFieldsParameters($container, $config['person_fields']);
$this->handleAccompanyingPeriodsFieldsParameters($container, $config['accompanying_periods_fields']);
$this->handleHouseholdFieldsParameters($container, $config['household_fields']);
$container->setParameter(
'chill_person.allow_multiple_simultaneous_accompanying_periods',
@@ -132,6 +136,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
'chill_accompanying_periods' => [
'fields' => $config['accompanying_periods_fields'],
],
'chill_household' => [
'fields' => $config['household_fields'],
],
],
'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,
'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)
{
if (\array_key_exists('enabled', $config)) {

View File

@@ -83,9 +83,11 @@ class Configuration implements ConfigurationInterface
->append($this->addFieldNode('accompanying_period'))
->append($this->addFieldNode('memo'))
->append($this->addFieldNode('number_of_children'))
->append($this->addFieldNode('number_of_dependents', 'hidden'))
->append($this->addFieldNode('acceptEmail'))
->append($this->addFieldNode('deathdate'))
->append($this->addFieldNode('employment_status', 'hidden'))
->append($this->addFieldNode('administrative_status', 'hidden'))
->arrayNode('alt_names')
->defaultValue([])
->arrayPrototype()
@@ -108,6 +110,12 @@ class Configuration implements ConfigurationInterface
->end()
->end() // children for 'person_fields', parent = array 'person_fields'
->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')
->canBeDisabled()
->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;
use Chill\PersonBundle\Repository\EmploymentStatusRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
#[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')]
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])]
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'])]
#[Serializer\Groups(['docgen:read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: false)]
@@ -98,6 +110,16 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
return $this->numberOfChildren;
}
public function getNumberOfDependents(): ?int
{
return $this->numberOfDependents;
}
public function getNumberOfDependentsWithDisabilities(): ?int
{
return $this->numberOfDependentsWithDisabilities;
}
public function getStartDate(): ?\DateTimeImmutable
{
return $this->startDate;
@@ -142,6 +164,20 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
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
{
$this->startDate = $startDate;

View File

@@ -304,6 +304,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
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.
*/
@@ -777,6 +784,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->addresses;
}
public function getAdministrativeStatus(): ?AdministrativeStatus
{
return $this->administrativeStatus;
}
/**
* Return the age of a person, calculated at the date 'now'.
*
@@ -1420,6 +1432,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this;
}
public function setAdministrativeStatus(?AdministrativeStatus $administrativeStatus): self
{
$this->administrativeStatus = $administrativeStatus;
return $this;
}
public function setAcceptSMS(bool $acceptSMS): self
{
$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\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
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)
{
@@ -42,7 +51,19 @@ class HouseholdCompositionType extends AbstractType
->add('numberOfChildren', IntegerType::class, [
'required' => true,
'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, [
'required' => false,
]);

View File

@@ -26,6 +26,7 @@ use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonPhone;
use Chill\PersonBundle\Form\Type\PersonAltNameType;
use Chill\PersonBundle\Form\Type\PersonPhoneType;
use Chill\PersonBundle\Form\Type\PickAdministrativeStatusType;
use Chill\PersonBundle\Form\Type\PickEmploymentStatusType;
use Chill\PersonBundle\Form\Type\PickGenderType;
use Chill\PersonBundle\Form\Type\Select2MaritalStatusType;
@@ -108,6 +109,11 @@ class PersonType extends AbstractType
->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']) {
$builder->add('placeOfBirth', TextType::class, [
'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]);
}
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', [
'route' => 'chill_crud_person_resource-kind_index',
])->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();
}
public function findAllOrdered(): array
{
return $this->repository->createQueryBuilder('si')
->orderBy('si.ordering', 'ASC')
->getQuery()
->getResult();
}
/**
* @return array|SocialAction[]
*/

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