Compare commits

..

19 Commits

Author SHA1 Message Date
801f799e45 Merge branch '125-list-person-on-course' into testing 2023-07-11 17:04:17 +02:00
98b8f3dcff Merge branch '118-design-filterOrder' into testing 2023-07-11 17:03:23 +02:00
a333a0312a Merge branch 'issue719_filter_activities_version_2' into testing 2023-07-11 17:02:44 +02:00
0288fd22cf Merge branch '128-export-group-activity-by-localisation' into testing 2023-07-11 17:02:33 +02:00
1dd4398c43 Merge branch '129-export-filter-course-having-activity' into testing 2023-07-11 17:02:19 +02:00
4f4b3dbb44 Merge branch 'issue719_filter_activities_version_2' into testing 2023-07-07 13:29:28 +02:00
1c3e6e0dba Merge remote-tracking branch 'origin/118-design-filterOrder' into testing 2023-07-07 13:29:11 +02:00
e7ca81e057 Merge branch 'master' into testing 2023-07-07 13:27:02 +02:00
9687debb57 Merge remote-tracking branch 'origin/master' into testing 2023-06-27 21:11:09 +02:00
769504c497 Merge branch 'issue719_filter_activities_version_2' into testing 2023-06-23 13:10:17 +02:00
811364e139 Merge branch '110-export-editable' into testing 2023-06-23 13:09:43 +02:00
deffc5e4db Merge branch '103-document-page' into testing 2023-06-18 22:03:51 +02:00
40ecaab5b4 Merge branch '110-export-editable' into testing 2023-06-18 22:03:36 +02:00
f7be53f790 Merge remote-tracking branch 'origin/master' into testing 2023-06-18 22:02:14 +02:00
6fb01b19ec Merge branch 'feature/change-parcours-status' into testing 2023-04-28 15:55:51 +02:00
b8ecff4f08 Merge branch 'exports/filters-on-work-date' into testing 2023-04-28 14:38:36 +02:00
4c340dd086 Merge branch 'feature/change-parcours-status' into testing 2023-04-28 14:38:28 +02:00
8f1955c536 Merge branch 'master' into testing 2023-04-28 14:38:08 +02:00
c9c15cdd56 Merge remote-tracking branch 'origin/testing' into testing 2023-04-28 14:38:00 +02:00
58 changed files with 188 additions and 1431 deletions

View File

@@ -0,0 +1,5 @@
kind: Feature
body: '[export] Add a list for people with their associated course'
time: 2023-07-07T12:36:09.596469063+02:00
custom:
Issue: "125"

View File

@@ -0,0 +1,6 @@
kind: Feature
body: '[export] Add ordering by person''s lastname or course opening date in list
which concerns accompanying course or peoples'
time: 2023-07-07T12:41:32.112725962+02:00
custom:
Issue: ""

View File

@@ -0,0 +1,5 @@
kind: Feature
body: '[Export] allow to group activities by localisation'
time: 2023-07-11T15:00:55.770070399+02:00
custom:
Issue: "128"

View File

@@ -0,0 +1,5 @@
kind: Feature
body: '[export] Add a filter "filter course having an activity between two dates"'
time: 2023-07-11T15:59:29.065329834+02:00
custom:
Issue: "129"

View File

@@ -1,39 +0,0 @@
## v2.5.0 - 2023-07-14
### Feature
* Allow filtering on the basis of a user within general tasks lists
* ([#120](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/120)) Adding OrderFilter to the list of social actions.
* ([#125](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/125)) [export] Add a list for people with their associated course
* [export] Add ordering by person's lastname or course opening date in list which concerns accompanying course or peoples
* ([#128](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/128)) [Export] allow to group activities by localisation
* ([#129](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/129)) [export] Add a filter "filter course having an activity between two dates"
* ([#112](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/112)) [addresses] Add a cronjob to re-associate addresses with addresses reference every 6 hours
* Improve filtering layout
### Fixed
* reimplement the visualization of all calculator results
* ([#117](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/117)) Repair my unread notification list with actions and evaluations documents
* ([#126](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/126)) Correct bug in thirdparty API search query: simplify address joins clause for child and parent kind
### DX
* Documentation for database principles
* [cronjob] when a cronjob is executed, it may return an array of data that will be passed as argument on the next execution
### UX
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Better integration of address details button: look, position, title tag
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Show address detail button on person and household banners
* Improve residential address position on show onthefly modale
### Traduction francophone des principaux changements
* Ajout d'un filtre "par utilisateur" aux pages de tâche
* Filtre des actions d'accompagnement par date, type, intervenant
* export: liste des usagers concernés avec détail de leurs parcours
* export: ajout d'un regroupement des échanges par localisation
* export: ajout d'un filtre "parcours ayant reçu un échange entre deux dates"
* ajout d'une tâche cron pour associer les adresses à une adresse de référence
* correction: réparation de la liste des notifications sur la page d'accueil, dans le cas où une notification concerne une action ou un document dans une évaluation
* correction: réparation de la recherche des tiers ayant des codes postaux similaires entre les parents et enfants
* meilleure intégration du bouton "détail d'une adresse": améliration de la taille et de la position
* bouton permettant de visualiser les détails d'une adresse (modale avec carte) dans la bannière "Usager" et "Ménage"
* amélioration de la modale permettant de voir les détails d'un usager: les adresses de résidence sont dans la continuité des autres adresses, et non plus dans une colonne séparée
* améliore le design et l'expérience utilisateur des filtres

View File

@@ -1,3 +0,0 @@
## v2.5.1 - 2023-07-14
### Fixed
* [collate addresses] block collating addresses to another address reference where the address reference is already the best match

View File

@@ -1,3 +0,0 @@
## v2.5.2 - 2023-07-15
### Fixed
* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead)

View File

@@ -1,3 +0,0 @@
## v2.5.3 - 2023-07-20
### Fixed
* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous.

View File

@@ -7,7 +7,7 @@ versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
kindFormat: '### {{.Kind}}'
# Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description.
changeFormat: >-
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if and (.Custom.Long) (not (eq .Custom.Long "")) }}
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if not (eq .Custom.Long "") }}
{{ .Custom.Long }}{{ end }}
custom:

View File

@@ -6,58 +6,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
## v2.5.3 - 2023-07-20
### Fixed
* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous.
## v2.5.2 - 2023-07-15
### Fixed
* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead)
## v2.5.1 - 2023-07-14
### Fixed
* [collate addresses] block collating addresses to another address reference where the address reference is already the best match
## v2.5.0 - 2023-07-14
### Feature
* Allow filtering on the basis of a user within general tasks lists
* ([#120](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/120)) Adding OrderFilter to the list of social actions.
* ([#125](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/125)) [export] Add a list for people with their associated course
* [export] Add ordering by person's lastname or course opening date in list which concerns accompanying course or peoples
* ([#128](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/128)) [Export] allow to group activities by localisation
* ([#129](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/129)) [export] Add a filter "filter course having an activity between two dates"
* ([#112](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/112)) [addresses] Add a cronjob to re-associate addresses with addresses reference every 6 hours
* Improve filtering layout
### Fixed
* reimplement the visualization of all calculator results
* ([#117](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/117)) Repair my unread notification list with actions and evaluations documents
* ([#126](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/126)) Correct bug in thirdparty API search query: simplify address joins clause for child and parent kind
### DX
* Documentation for database principles
* [cronjob] when a cronjob is executed, it may return an array of data that will be passed as argument on the next execution
### UX
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Better integration of address details button: look, position, title tag
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Show address detail button on person and household banners
* Improve residential address position on show onthefly modale
### Traduction francophone des principaux changements
* Ajout d'un filtre "par utilisateur" aux pages de tâche
* Filtre des actions d'accompagnement par date, type, intervenant
* export: liste des usagers concernés avec détail de leurs parcours
* export: ajout d'un regroupement des échanges par localisation
* export: ajout d'un filtre "parcours ayant reçu un échange entre deux dates"
* ajout d'une tâche cron pour associer les adresses à une adresse de référence
* correction: réparation de la liste des notifications sur la page d'accueil, dans le cas où une notification concerne une action ou un document dans une évaluation
* correction: réparation de la recherche des tiers ayant des codes postaux similaires entre les parents et enfants
* meilleure intégration du bouton "détail d'une adresse": améliration de la taille et de la position
* bouton permettant de visualiser les détails d'une adresse (modale avec carte) dans la bannière "Usager" et "Ménage"
* amélioration de la modale permettant de voir les détails d'un usager: les adresses de résidence sont dans la continuité des autres adresses, et non plus dans une colonne séparée
* améliore le design et l'expérience utilisateur des filtres
## v2.4.0 - 2023-07-07
### Feature

View File

@@ -1,80 +0,0 @@
# Contributing
Chill is an open source, community-driven project.
If you'd like to contribute, please read the following.
## What can you do ?
Chill is an open-source project driven by a community of developers, users and social workers. If you don't feel ready to contribute code or patches, reviewing issues and pull requests (PRs) can be a great start to get involved and give back.
If you don't have your own instance or don't want to use it, you can try to reproduce bugs using the instance https://demo.chill.social
## Core team
The core team is the group of developers that determine the direction and evolution of the Chill project. Their votes rule if the features and patches proposed by the community are approved or rejected.
All the Chill Core members are long-time contributors with solid technical expertise and they have demonstrated a strong commitment to drive the project forward.
The core team:
- elects his own members;
- merge pull requests;
### members
Project leader: [julienfastre](https://gitlab.com/julienfastre)
Core members:
- [tchama](https://gitlab.com/tchama)
- [LenaertsJ](https://gitlab.com/LenaertsJ)
- [nobohan](https://gitlab.com/nobohan)
### Becoming a project member
About once a year, the core team discusses the opportunity to invite new members. To become a core team member, you must:
- take part on the development for at least 6 month: propose multiple merge requests and participate to the peer review process;
- through this participation, demonstrate your technical skills and your knowledge of the software and any of their dependencies;
### Core Membership Revocation
A Symfony Core membership can be revoked for any of the following reasons:
- Refusal to follow the rules and policies stated in this document;
- Lack of activity for the past six months;
- Willful negligence or intent to harm the Chill project;
The decision is taken by the majority of project members.
## Code development rules
### Merge requests
Every merge request must contains:
- one more entries suitable for generating a changelog. This is done using the [changie utility](https://changie.dev);
- a comprehensible description of the changes;
- if applicable, automated tests should be adapted or created;
- the code style must pass the project's rules, and non phpstan errors must be raised nor rector refactoring suggestion.
The pipelines must pass.
In case of emergency, some rules may be temporarily ignored.
### Merge Request Voting Policy
- -1 votes must always be justified by technical and objective reasons;
- +1 (technically: approbation on the merge request) votes do not require justification, unless there is at least one -1 vote;
- Core members can change their votes as many times as they desire during the course of a merge request discussion;
- Core members are not allowed to vote on their own merge requests.
### Merge Request Merging Process
All code must be committed to the repository through merge requests, except for minor changes which can be committed directly to the repository.
### Release Policy
The Core members are also the release manager for every Chill version.

View File

@@ -1,84 +0,0 @@
.. database-principles:
Principes de la base de données
###############################
Cette page donne une compréhension globale de la base de donnée de Chill, et explique quelques détails d'implémentations qui permettent d'accélérer les traitements à partir de la base de donnée, ou de l'exploiter plus aisément.
Cette page est rédigée en français.
.. note::
La stabilité du schéma de la base de donnée n'est pas garantie.
Toutefois, ce dernier évolue relativement peu. Il est rare que des tables ou des colonnes soient supprimées ou renommées. Mais il n'est pas garanti que cela puisse arriver.
Généralités
===========
Une liste commentée de toutes les tables :download:`est disponible au format CSV <./database/table_list.csv`.
Schéma et conventions de nommage
--------------------------------
Au début de l'histoire de Chill, les schémas postgresql n'étaient pas exploités. Les données étaient stockées dans le schéma :code:`public`.
Par la suite, des nouveaux bundles sont apparus, et les tables ont été classées dans des schémas dédiés.
A l'heure actuelle:
- pour les anciens bundle, ceux qui ont déjà des tables dans le schéma public, les nouvelles tables sont ajoutées à ce schéma. Elles sont préfixées par :code:`chill_<nom du bundle>_`;
- pour les bundles plus récents, les tables sont créées dans le schéma dédié
Données avec de l'historicité
-----------------------------
Certaines données sont historisées:
- les référents d'un parcours;
- les statuts d'un parcours;
- la liaison entre les centres et les usagers;
- etc.
Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable.
Dans certains cas, la donnée actuelle (référent d'un parcours, par exemple) est également répétée au niveau de la table en elle-même. Par exemple, la table des parcours :code:`chill_person_accompanying_period` comporte une colonne :code:`step` (le statut du parcours) et :code:`user_id` (id du référent) en plus de l'historique. Bien que redondant, cela simplifie les traitements.
Relations particulières
=======================
Usagers, ménages, adresses
--------------------------
Les usagers ont une adresse au travers des ménages: dans l'interface, l'adresse est inscrite dans le dossier du ménage, et elle est "donnée" aux usagers membres du ménage, **et** qui partagent l'adresse de ce ménage. En effet, il est possible que des usagers "appartiennent" à un ménage sans y être domicilié: c'est le cas, par exemple, des enfants en garde alternée.
L'historique de l'appartenance des usagers au ménage est conservée, de même que l'historique des adresses pour un même ménage.
Les tables en jeu sont les suivantes:
- la table :code:`chill_person_person` liste les usagers;
- la table :code:`chill_person_household_members` liste les appartenances au ménage: il s'agit de la jointure entre les usagers et les ménages:
- les colonnes :code:`startDate` et :code:`endDate` indiquent la date de début et la date de fin de l'appartenance;
- la colonne :code:`shareHousehold` indique si l'utilisateur partage l'adresse du ménage (si oui, sa valeur est :code:`TRUE`)
- la table :code:`chill_person_household` liste les ménages
- la table :code:`chill_person_household_to_addresses` associe les ménages aux adresses;
- la table :code:`chill_main_address` contient les adresses, en indiquant la date de début de validité (:code:`validFrom`) et la fin de validité (:code:`validTo`).
Pour simplifier la résolution des adresses et des usagers, deux vues ont été mises en œuvre:
- la vue :code:`view_chill_person_household_address` reprend, pour chaque usager, l'historique des appartenances au ménage découpée par l'historique des adresses d'un ménage.
Autrement dit, une ligne est créée à chaque fois qu'un usager change de ménage, ou qu'un ménage change d'adresse. Il est donc possible de retrouver l'historique complet des adresses pour un usager donné via cette table.
- la vue :code:`view_chill_person_current_address` reprend l'adresse actuelle des usagers.
Adresses et unités géographiques
--------------------------------
Chill propose des statistiques sur la localisation des adresses par rapport à des zones géographiques (:code:`chill_main_geographical_unit`).
Comme la résolution géographique des adresses est coûteuse en CPU et en temps de traitement, une vue matérialisée a été créée: :code:`view_chill_main_address_geographical_unit`. Elle est rafraichie quotidiennement dans la base de donnée de production.
Liste des tables et commentaires
================================
Une liste commentée de toutes les tables :download:`est disponible au format CSV <./database/table_list.csv`.

View File

@@ -1,155 +0,0 @@
order,table_schema,table_name,commentaire
1,chill_3party,party_category,Catégorie de tiers
2,chill_3party,party_center,Association entre les tiers et les centres (déprécié)
3,chill_3party,party_profession,Profession du tiers (déprécié)
4,chill_3party,third_party,Tiers
5,chill_3party,thirdparty_category,association tiers - catégories
6,chill_asideactivity,asideactivity,Activités annexes
7,chill_asideactivity,asideactivitycategory,Catégories d'activités annexes
8,chill_budget,charge,Charges du budget
9,chill_budget,charge_type,Types de charges
10,chill_budget,resource,Ressources du budget
11,chill_budget,resource_type,Types de ressources
12,chill_calendar,calendar,Rendez-vous
13,chill_calendar,calendar_doc,Document du rendez-vous
14,chill_calendar,calendar_range,Plage de disponibilité
15,chill_calendar,calendar_to_persons,association rendez-vous - usagers
16,chill_calendar,calendar_to_thirdparties,association rendez-vous - tiers
17,chill_calendar,cancel_reason,Motifs d'annulations
18,chill_calendar,invite,Invitation aux rendez-vous
19,chill_doc,accompanyingcourse_document,Documents associés aux parcours
20,chill_doc,document_category,Catégories de documents
21,chill_doc,person_document,Documents associés à l'usagers
22,chill_doc,stored_object,Documents
23,chill_task,recurring_task,Tâches récurrentes (non utilisé)
24,chill_task,single_task,Tâches
25,chill_task,single_task_place_event,Historique des transitions des tâches
26,chill_vendee,adressederelais,
27,chill_vendee,center_polygon
28,chill_vendee,entourage,
29,chill_vendee,geographical_unit
30,chill_vendee,geographical_unit_association
31,chill_vendee,mobilite
32,chill_vendee,niveauetude
33,chill_vendee,security_profile
34,chill_vendee,security_profile_action
35,chill_vendee,security_profile_jobs
36,chill_vendee,situationprofessionelle
37,chill_vendee,statutlogement
38,chill_vendee,tempsdetravail
39,chill_vendee,titredesejour
40,chill_vendee,vendee_person
41,chill_vendee,vendee_person_mineur
42,chill_vendee,vendeeperson_entourage
43,chill_vendee,vendeepersonmineur_adressederelais
44,public,accompanying_periods_scopes,Services associés aux parcours
45,public,activity,Échanges
46,public,activity_activityreason,s
47,public,activity_person,
48,public,activity_storedobject,
49,public,activity_thirdparty,
50,public,activity_user,
51,public,activityreason,Sujets d'échange
52,public,activityreasoncategory,Catégories de sujets
53,public,activitytpresence,Présence aux échanges
54,public,activitytype,Types d'échanges
55,public,activitytypecategory,Catégories de types d'échanges
56,public,centers,"Centres (territoires, agences, etc.)"
57,public,chill_activity_activity_chill_person_socialaction,
58,public,chill_activity_activity_chill_person_socialissue
59,public,chill_docgen_template,Gabarits de documents
60,public,chill_main_address,Adresses
61,public,chill_main_address_legacy,Anciennes adresses (dépréciés)
62,public,chill_main_address_reference,Adresses de référence
63,public,chill_main_civility,Civilités
64,public,chill_main_cronjob_execution,Dernière exécution des tâche cron
65,public,chill_main_geographical_unit,Unités géographiques
66,public,chill_main_geographical_unit_layer,Couches d'unités géographiques
67,public,chill_main_location,Localisations
68,public,chill_main_location_type,Types de localisations
69,public,chill_main_notification,Notifications
70,public,chill_main_notification_addresses_unread
71,public,chill_main_notification_addresses_user
72,public,chill_main_notification_comment,
73,public,chill_main_postal_code,Code postaux
74,public,chill_main_saved_export,Exports enregistrés
75,public,chill_main_user_job,Métiers
76,public,chill_main_workflow_entity,Workflows
77,public,chill_main_workflow_entity_comment
78,public,chill_main_workflow_entity_step,Etapes du workflow
79,public,chill_main_workflow_entity_step_cc_user,
80,public,chill_main_workflow_entity_step_user
81,public,chill_main_workflow_entity_step_user_by_accesskey,
82,public,chill_main_workflow_entity_subscriber_to_final,
83,public,chill_main_workflow_entity_subscriber_to_step
84,public,chill_person_accompanying_period,Parcours d'accompagnement
85,public,chill_person_accompanying_period_closingmotive,Motifs de cloture des parcours
86,public,chill_person_accompanying_period_comment,Commentaires des parcours
87,public,chill_person_accompanying_period_location_history,Historique de la localisatio ndes parcours
88,public,chill_person_accompanying_period_origin,Origine des parcours
89,public,chill_person_accompanying_period_participation,Appartenance des usagers au parcours
90,public,chill_person_accompanying_period_resource,Personnes ressources d'un parcours
91,public,chill_person_accompanying_period_social_issues,
92,public,chill_person_accompanying_period_step_history
93,public,chill_person_accompanying_period_user_history
94,public,chill_person_accompanying_period_work,Actions d'accompagnements
95,public,chill_person_accompanying_period_work_evaluation,Évaluations (dans les actions d'accompagnements)
96,public,chill_person_accompanying_period_work_evaluation_document,Documents des évaluations
97,public,chill_person_accompanying_period_work_goal,Objectifs d'une actions
98,public,chill_person_accompanying_period_work_goal_result,Objectifs et résultats d'une action
99,public,chill_person_accompanying_period_work_person,Usagers associés à une actions
100,public,chill_person_accompanying_period_work_referrer,Référents d'une actions
101,public,chill_person_accompanying_period_work_result,Résultats d'une action
102,public,chill_person_accompanying_period_work_third_party,Tiers traitants d'une action
103,public,chill_person_alt_name,"Noms supplémentaires d'un usager (nom marital, etc.)"
104,public,chill_person_household,Ménages
105,public,chill_person_household_composition,
106,public,chill_person_household_composition_type,Types de composition de ménage
107,public,chill_person_household_members,Membres du ménages
108,public,chill_person_household_position,Positions dans le ménage
109,public,chill_person_household_to_addresses,Association adresses - ménages
110,public,chill_person_marital_status,Etats civils
111,public,chill_person_not_duplicate,
112,public,chill_person_person,Usagers
113,public,chill_person_person_center_history,Historique des centres d'un usagers
114,public,chill_person_persons_to_addresses,Déprécié
115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager
116,public,chill_person_relations,Types de relations de filiation
117,public,chill_person_relationships,Relations de filiations
118,public,chill_person_residential_address,Adresses de résidences
119,public,chill_person_resource,Personnes ressources (pour les personnes)
120,public,chill_person_resource_kind,Type de personnes ressources
121,public,chill_person_social_action,Liste des actions d'accompagnement
122,public,chill_person_social_action_goal,Objectifs associés à une action
123,public,chill_person_social_action_result,Résultats associés à une action
124,public,chill_person_social_issue,Problématiques sociales
125,public,chill_person_social_work_evaluation,Evaluations disponibles
126,public,chill_person_social_work_evaluation_action,Associations entre les évaluations et les actions
127,public,chill_person_social_work_goal,Objectifs disponibles pour une actions
128,public,chill_person_social_work_goal_result,Objectifs et résultats disponible pour une action
129,public,chill_person_social_work_result,Résultats disponibles pour une action
130,public,country,Pays
131,public,custom_field_long_choice_options,
132,public,customfield
133,public,customfieldsdefaultgroup
134,public,customfieldsgroup
135,public,geography_columns,Table liée à postgis
136,public,geometry_columns,Table liée à postgis
137,public,group_centers,
138,public,language,Langues
139,public,messenger_messages,Table système
140,public,migration_versions,Table système
141,public,permission_groups
142,public,permissionsgroup_rolescope
143,public,persons_spoken_languages
144,public,regroupment,Regroupement de centres
145,public,regroupment_center,
146,public,role_scopes,
147,public,scopes,Services
148,public,spatial_ref_sys,Table système (postgis)
149,public,user_groupcenter,
150,public,users,Utilisateurs
151,public,view_chill_person_accompanying_period_info,
152,public,view_chill_person_current_address
153,public,view_chill_person_household_address
154,public,view_chill_person_person_center_history_current
Can't render this file because it has a wrong number of fields in line 28.

View File

@@ -36,7 +36,6 @@ As Chill rely on the `symfony <http://symfony.com>`_ framework, reading the fram
Assets <assets.rst>
Cron Jobs <cronjob.rst>
Info about entities <entity-info.rst>
Info about database (in French) <database-principles.rst>
Layout and UI
**************

View File

@@ -1,12 +1,11 @@
{% macro table_elements(elements, type) %}
{% macro table_elements(elements, family) %}
<table class="table table-bordered border-dark budget-table">
<thead>
<tr>
<th class="{{ type }} el-type">{{ 'Budget element type'|trans }}</th>
<th class="{{ type }}">{{ 'Amount'|trans }}</th>
<th class="{{ type }}">{{ 'Validity period'|trans }}</th>
<th class="{{ type }}">&nbsp;</th>
<th class="{{ family }} el-type">{{ 'Budget element type'|trans }}</th>
<th class="{{ family }}">{{ 'Amount'|trans }}</th>
<th class="{{ family }}">{{ 'Validity period'|trans }}</th>
<th class="{{ family }}">&nbsp;</th>
</tr>
</thead>
<tbody>
@@ -39,17 +38,17 @@
<ul class="record_actions">
{% if is_granted('CHILL_BUDGET_ELEMENT_SEE', f) %}
<li>
<a href="{{ path('chill_budget_' ~ type ~ '_view', { 'id': f.id } ) }}" class="btn btn-sm btn-show"></a>
<a href="{{ path('chill_budget_' ~ family ~ '_view', { 'id': f.id } ) }}" class="btn btn-sm btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_BUDGET_ELEMENT_UPDATE', f) %}
<li>
<a href="{{ path('chill_budget_' ~ type ~'_edit', { 'id': f.id } ) }}" class="btn btn-sm btn-edit"></a>
<a href="{{ path('chill_budget_' ~ family ~'_edit', { 'id': f.id } ) }}" class="btn btn-sm btn-edit"></a>
</li>
{% endif %}
{% if is_granted('CHILL_BUDGET_ELEMENT_DELETE', f) %}
<li>
<a href="{{ path('chill_budget_' ~ type ~ '_delete', { 'id': f.id } ) }}" class="btn btn-sm btn-delete"></a>
<a href="{{ path('chill_budget_' ~ family ~ '_delete', { 'id': f.id } ) }}" class="btn btn-sm btn-delete"></a>
</li>
{% endif %}
</ul>
@@ -70,7 +69,7 @@
</table>
{% endmacro %}
{% macro table_results(actualCharges, actualResources, results) %}
{% macro table_results(actualCharges, actualResources) %}
{% set totalCharges = 0 %}
{% for c in actualCharges %}
@@ -98,20 +97,6 @@
{{ result|format_currency('EUR') }}
</td>
</tr>
{% for result in results %}
<tr>
<td>{{ result.label }}</td>
<td>
{% if result.type == 'currency' %}
{{ result.result|format_currency('EUR') }}
{% elseif result.type == 'percentage' %}
{{ result.result|round(2, 'ceil') ~ '%' }}
{% else %}
{{ result.result|round(2, 'common') }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endmacro %}

View File

@@ -25,7 +25,7 @@
<div class="mt-5">
<h3 class="subtitle">{{ 'Budget calculator'|trans }}</h3>
{{ table_results(charges, resources, results) }}
{{ table_results(charges, resources) }}
</div>
{% if is_granted('CHILL_BUDGET_ELEMENT_CREATE', person) %}

View File

@@ -147,9 +147,6 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
return $query;
}
$query->addWhereClause(implode(" OR ", $or), $orParams, $orTypes);
return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
}

View File

@@ -46,7 +46,8 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{
$course = $this->getRelatedEntity($entityWorkflow)?->getCourse();
$course = $this->getRelatedEntity($entityWorkflow)
->getCourse();
$persons = [];
if (null !== $course) {

View File

@@ -19,13 +19,5 @@ interface CronJobInterface
public function getKey(): string;
/**
* Execute the cronjob
*
* If data is returned, this data is passed as argument on the next execution
*
* @param array $lastExecutionData the data which was returned from the previous execution
* @return array|null optionally return an array with the same data than the previous execution
*/
public function run(array $lastExecutionData): null|array;
public function run(): void;
}

View File

@@ -14,7 +14,6 @@ namespace Chill\MainBundle\Cron;
use Chill\MainBundle\Entity\CronJobExecution;
use Chill\MainBundle\Repository\CronJobExecutionRepositoryInterface;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Psr\Log\LoggerInterface;
@@ -47,8 +46,6 @@ class CronManager implements CronManagerInterface
private const UPDATE_BEFORE_EXEC = 'UPDATE ' . CronJobExecution::class . ' cr SET cr.lastStart = :now WHERE cr.key = :key';
private const UPDATE_LAST_EXECUTION_DATA = 'UPDATE ' . CronJobExecution::class . ' cr SET cr.lastExecutionData = :data WHERE cr.key = :key';
private CronJobExecutionRepositoryInterface $cronJobExecutionRepository;
private EntityManagerInterface $entityManager;
@@ -88,9 +85,6 @@ class CronManager implements CronManagerInterface
foreach ($orderedJobs as $job) {
if ($job->canRun($lasts[$job->getKey()] ?? null)) {
if (array_key_exists($job->getKey(), $lasts)) {
$executionData = $lasts[$job->getKey()]->getLastExecutionData();
$this->entityManager
->createQuery(self::UPDATE_BEFORE_EXEC)
->setParameters([
@@ -102,17 +96,12 @@ class CronManager implements CronManagerInterface
$execution = new CronJobExecution($job->getKey());
$this->entityManager->persist($execution);
$this->entityManager->flush();
$executionData = $execution->getLastExecutionData();
}
$this->entityManager->clear();
// note: at this step, the entity manager does not have any entity CronJobExecution
// into his internal memory
try {
$this->logger->info(sprintf('%sWill run job', self::LOG_PREFIX), ['job' => $job->getKey()]);
$result = $job->run($executionData);
$job->run();
$this->entityManager
->createQuery(self::UPDATE_AFTER_EXEC)
@@ -123,14 +112,6 @@ class CronManager implements CronManagerInterface
])
->execute();
if (null !== $result) {
$this->entityManager
->createQuery(self::UPDATE_LAST_EXECUTION_DATA)
->setParameter('data', $result, Types::JSON)
->setParameter('key', $job->getKey(), Types::STRING)
->execute();
}
$this->logger->info(sprintf('%sSuccessfully run job', self::LOG_PREFIX), ['job' => $job->getKey()]);
return;
@@ -152,7 +133,7 @@ class CronManager implements CronManagerInterface
}
/**
* @return array{0: array<CronJobInterface>, 1: array<string, CronJobExecution>}
* @return array<0: CronJobInterface[], 1: array<string, CronJobExecution>>
*/
private function getOrderedJobs(): array
{
@@ -193,7 +174,7 @@ class CronManager implements CronManagerInterface
{
foreach ($this->jobs as $job) {
if ($job->getKey() === $forceJob) {
$job->run([]);
$job->run();
}
}
}

View File

@@ -31,6 +31,7 @@ class CronJobExecution
private string $key;
/**
* @var DateTimeImmutable
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": null})
*/
private ?DateTimeImmutable $lastEnd = null;
@@ -45,11 +46,6 @@ class CronJobExecution
*/
private ?int $lastStatus = null;
/**
* @ORM\Column(type="json", options={"default": "'{}'::jsonb", "jsonb": true})
*/
private array $lastExecutionData = [];
public function __construct(string $key)
{
$this->key = $key;
@@ -96,16 +92,4 @@ class CronJobExecution
return $this;
}
public function getLastExecutionData(): array
{
return $this->lastExecutionData;
}
public function setLastExecutionData(array $lastExecutionData): CronJobExecution
{
$this->lastExecutionData = $lastExecutionData;
return $this;
}
}

View File

@@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type\Listing;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
@@ -115,28 +114,6 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
$builder->add($singleCheckBoxBuilder);
}
if ([] !== $helper->getUserPickers()) {
$userPickersBuilder = $builder->create('user_pickers', null, ['compound' => true]);
foreach ($helper->getUserPickers() as $name => [
'label' => $label, 'options' => $opts
]) {
$userPickersBuilder->add(
$name,
PickUserDynamicType::class,
[
'multiple' => true,
'label' => $label,
...$opts,
]
);
}
$builder->add($userPickersBuilder);
}
}
public static function buildCheckboxChoices(array $choices, array $trans = []): array

View File

@@ -66,10 +66,6 @@ export default {
return appMessages.fr.the_activity;
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod':
return appMessages.fr.the_course;
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork':
return appMessages.fr.the_action;
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument':
return appMessages.fr.the_evaluation_document;
case 'Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow':
return appMessages.fr.the_workflow;
default:
@@ -82,10 +78,6 @@ export default {
return `/fr/activity/${n.relatedEntityId}/show`
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod':
return `/fr/parcours/${n.relatedEntityId}`
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork':
return `/fr/person/accompanying-period/work/${n.relatedEntityId}/show`
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument':
return `/fr/person/accompanying-period/work/evaluation/document/${n.relatedEntityId}/show`
case 'Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow':
return `/fr/main/workflow/${n.relatedEntityId}/show`
default:

View File

@@ -46,7 +46,6 @@ const appMessages = {
the_course: "le parcours",
the_action: "l'action",
the_evaluation: "l'évaluation",
the_evaluation_document: "le document",
the_task: "la tâche",
the_workflow: "le workflow",
StartDate: "Date d'ouverture",

View File

@@ -1,7 +1,7 @@
<template>
<span v-if="data.working_ref_status === 'to_review'" class="badge bg-danger address-details-button-warning">L'adresse de référence a été modifiée</span>
<a v-if="data.loading === false" @click.prevent="clickOrOpen" class="btn btn-sm address-details-button" title="Plus de détails">
<span class="fa fa-map-o"></span>
<a v-if="data.loading === false" @click.prevent="clickOrOpen" class="btn btn-misc address-details-button">
<span class="fa fa-map"></span> <!-- button -->
</a>
<span v-if="data.loading" class="fa fa-spin fa-spinner "></span>
<AddressModal :address="data.working_address" @update-address="onUpdateAddress" ref="address_modal"></AddressModal>

View File

@@ -1,6 +1,6 @@
<template>
<component :is="component" class="chill-entity entity-address">
<component :is="component" class="chill-entity entity-address my-3">
<component :is="component" class="address" :class="multiline">

View File

@@ -18,7 +18,6 @@
</div>
{% endif %}
</div>
{% if form.dateRanges is defined %}
{% set btnSubmit = 1 %}
{% if form.dateRanges|length > 0 %}
@@ -41,7 +40,6 @@
{% endfor %}
{% endif %}
{% endif %}
{% if form.checkboxes is defined %}
{% set btnSubmit = 1 %}
{% if form.checkboxes|length > 0 %}
@@ -58,7 +56,6 @@
{% endfor %}
{% endif %}
{% endif %}
{% if form.entity_choices is defined %}
{% set btnSubmit = 1 %}
{% if form.entity_choices |length > 0 %}
@@ -77,25 +74,6 @@
{% endfor %}
{% endif %}
{% endif %}
{% if form.user_pickers is defined %}
{% set btnSubmit = 1 %}
{% if form.user_pickers.children|length > 0 %}
{% for name, options in form.user_pickers %}
<div class="row my-2">
{% if form.user_pickers[name].vars.label is not same as(false) %}
{{ form_label(form.user_pickers[name]) }}
{% else %}
{{ form_label(form.user_pickers[name].vars.label) }}
{% endif %}
<div class="col-sm-8 pt-2">
{{ form_widget(form.user_pickers[name]) }}
</div>
</div>
{% endfor %}
{% endif %}
{% endif %}
{% if form.single_checkboxes is defined %}
{% set btnSubmit = 1 %}
{% for name, _o in form.single_checkboxes %}
@@ -113,10 +91,8 @@
<button type="submit" class="btn btn-sm btn-misc"><i class="fa fa-fw fa-filter"></i>{{ 'Filter'|trans }}</button>
</div>
{% endif %}
</div>
</div>
{% if active|length > 0 %}
<div class="activeFilters mt-3">
{% for f in active %}

View File

@@ -1,150 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Service\AddressGeographicalUnit;
use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
final readonly class CollateAddressWithReferenceOrPostalCode implements CollateAddressWithReferenceOrPostalCodeInterface
{
private const LOG_PREFIX = '[collate addresses] ';
/**
* For the address having an "invented" postal code, find the postal code "reference" with the same code,
* and the most similar name. When two reference code match, we add
*
* This query intentionally includes also address with reference, as the reference may be wrong.
*/
private const FORCE_ORIGINAL_POSTAL_CODE = <<<'SQL'
WITH recollate AS (
SELECT * FROM (
SELECT cma.id AS address_id, cmpc.id, cmpc.label, cmpc.code, cmpc_reference.id AS cmpc_reference_id, cmpc_reference.label, cmpc_reference.code,
RANK() OVER (PARTITION BY cma.id ORDER BY SIMILARITY(cmpc.label, cmpc_reference.label) DESC, cmpc_reference.id ASC) AS ranked
FROM
chill_main_address cma JOIN chill_main_postal_code cmpc on cma.postcode_id = cmpc.id,
chill_main_postal_code cmpc_reference
WHERE
-- use only postal code which are reference
cmpc_reference.id != cmpc.id AND cmpc_reference.origin = 0
-- only where cmpc is created manually
AND cmpc.origin != 0
-- only when postal code match
AND TRIM(REPLACE(LOWER(cmpc.code), ' ', '')) = LOWER(cmpc_reference.code)
AND cmpc.country_id = cmpc_reference.country_id
AND cma.id > :since_id -- to set the first id
) sq
WHERE ranked = 1)
UPDATE chill_main_address SET postcode_id = cmpc_reference_id FROM recollate WHERE recollate.address_id = chill_main_address.id;
SQL;
/**
* associate the address with the most similar address reference.
*
* This query intentionally ignores the existing addressreference_id, to let fixing the address match the
* most similar address reference.
*/
private const FORCE_MOST_SIMILAR_ADDRESS_REFERENCE = <<<'SQL'
WITH recollate AS (
SELECT * FROM (
SELECT cma.id AS address_id, cma.streetnumber, cma.street, cmpc.code, cmpc.label, cmar.id AS address_reference_id, cmar.streetnumber, cmar.street, cmpc_reference.code, cmpc_reference.label,
similarity(cma.street, cmar.street),
RANK() OVER (PARTITION BY cma.id ORDER BY SIMILARITY (cma.street, cmar.street) DESC, SIMILARITY (cma.streetnumber, cmar.streetnumber), cmar.id ASC) AS ranked
FROM
chill_main_address cma
JOIN chill_main_postal_code cmpc on cma.postcode_id = cmpc.id,
chill_main_address_reference cmar JOIN chill_main_postal_code cmpc_reference ON cmar.postcode_id = cmpc_reference.id
WHERE
-- only if cmpc is a reference (must be matched before executing this query)
cma.postcode_id = cmar.postcode_id
-- join cmpc to cma
AND SIMILARITY(LOWER(cma.street), LOWER(cmar.street)) > 0.6 AND LOWER(cma.streetnumber) = LOWER(cmar.streetnumber)
-- only addresses which match the address reference - let the user decide if the reference has changed
AND cma.refstatus = 'match'
-- only the most recent
AND cma.id > :since_id
) AS sq
WHERE ranked = 1
)
UPDATE chill_main_address SET addressreference_id = recollate.address_reference_id FROM recollate WHERE chill_main_address.id = recollate.address_id;
SQL;
/**
* Update the point's address with the:
*
* - address reference point, if the address match the reference with sufficient similarity
* - or the postcal code center
*/
private const UPDATE_POINT = <<<'SQL'
WITH address_geom AS (
SELECT cma.id AS address_id, COALESCE(cmar.point, cmpc.center) AS point
FROM chill_main_address cma
LEFT JOIN chill_main_address_reference cmar ON cma.addressreference_id = cmar.id AND similarity(cma.street, cmar.street) > 0.6 AND LOWER(cma.streetnumber) = LOWER(cmar.streetnumber)
LEFT JOIN chill_main_postal_code cmpc ON cma.postcode_id = cmpc.id
WHERE cma.id > :since_id
)
UPDATE chill_main_address SET point = address_geom.point FROM address_geom WHERE address_geom.address_id = chill_main_address.id
SQL;
private const MAX_ADDRESS_ID = <<<'SQL'
SELECT MAX(id) AS max_id FROM chill_main_address;
SQL;
public function __construct(
private Connection $connection,
private LoggerInterface $logger,
) {
}
/**
* @throws \Throwable
*/
public function __invoke(int $sinceId = 0): int
{
try {
[
$postCodeSetReferenceFromMostSimilar,
$addressReferenceMatch,
$pointUpdates,
$lastId,
] = $this->connection->transactional(function () use ($sinceId) {
$postCodeSetReferenceFromMostSimilar = $this->connection->executeStatement(self::FORCE_ORIGINAL_POSTAL_CODE, ['since_id' => $sinceId]);
$addressReferenceMatch = $this->connection->executeStatement(self::FORCE_MOST_SIMILAR_ADDRESS_REFERENCE, ['since_id' => $sinceId]);
$pointUpdates = $this->connection->executeStatement(self::UPDATE_POINT, ['since_id' => $sinceId]);
$lastId = $this->connection->fetchOne(self::MAX_ADDRESS_ID);
return [
$postCodeSetReferenceFromMostSimilar,
$addressReferenceMatch,
$pointUpdates,
$lastId,
];
});
} catch (\Throwable $e) {
$this->logger->error(self::LOG_PREFIX . "error while re-collating addresses", [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
$this->logger->info(self::LOG_PREFIX . "Collate the addresses with reference", [
'set_postcode_from_most_similar' => $postCodeSetReferenceFromMostSimilar,
'address_reference_match' => $addressReferenceMatch,
'point_update' => $pointUpdates,
'since_id' => $sinceId,
'last_id' => $lastId,
]);
return $lastId;
}
}

View File

@@ -1,50 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Service\AddressGeographicalUnit;
use Chill\MainBundle\Cron\CronJobInterface;
use Chill\MainBundle\Entity\CronJobExecution;
use Symfony\Component\Clock\ClockInterface;
final readonly class CollateAddressWithReferenceOrPostalCodeCronJob implements CronJobInterface
{
private const LAST_MAX_ID = 'last-max-id';
public function __construct(
private ClockInterface $clock,
private CollateAddressWithReferenceOrPostalCodeInterface $collateAddressWithReferenceOrPostalCode,
) {
}
public function canRun(?CronJobExecution $cronJobExecution): bool
{
if (null === $cronJobExecution) {
return true;
}
$now = $this->clock->now();
return $now->sub(new \DateInterval('PT6H')) > $cronJobExecution->getLastStart();
}
public function getKey(): string
{
return 'collate-address';
}
public function run(array $lastExecutionData): null|array
{
$maxId = ($this->collateAddressWithReferenceOrPostalCode)($lastExecutionData[self::LAST_MAX_ID] ?? 0);
return [self::LAST_MAX_ID => $maxId];
}
}

View File

@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Service\AddressGeographicalUnit;
interface CollateAddressWithReferenceOrPostalCodeInterface
{
/**
* @throws \Throwable
*/
public function __invoke(int $sinceId = 0): int;
}

View File

@@ -49,10 +49,8 @@ class RefreshAddressToGeographicalUnitMaterializedViewCronJob implements CronJob
return 'refresh-materialized-view-address-to-geog-units';
}
public function run(array $lastExecutionData): null|array
public function run(): void
{
$this->connection->executeQuery('REFRESH MATERIALIZED VIEW view_chill_main_address_geographical_unit');
return null;
}
}

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Listing;
use Chill\MainBundle\Templating\Entity\UserRender;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPathInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -21,7 +20,6 @@ final readonly class FilterOrderGetActiveFilterHelper
public function __construct(
private TranslatorInterface $translator,
private PropertyAccessorInterface $propertyAccessor,
private UserRender $userRender,
) {
}
@@ -75,12 +73,6 @@ final readonly class FilterOrderGetActiveFilterHelper
}
}
foreach ($filterOrderHelper->getUserPickers() as $name => ['label' => $label, 'options' => $options]) {
foreach ($filterOrderHelper->getUserPickerData($name) as $user) {
$result[] = ['value' => $this->userRender->renderString($user, []), 'label' => (string) $label, 'position' => FilterOrderPositionEnum::UserPicker->value, 'name' => $name];
}
}
foreach ($filterOrderHelper->getSingleCheckbox() as $name => ['label' => $label]) {
if (true === $filterOrderHelper->getSingleCheckboxData($name)) {
$result[] = ['label' => '', 'value' => $this->translator->trans($label), 'position' => FilterOrderPositionEnum::SingleCheckbox->value, 'name' => $name];

View File

@@ -11,14 +11,19 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Listing;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\Listing\FilterOrderType;
use DateTimeImmutable;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\PropertyAccess\PropertyPathInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_merge;
use function count;
final class FilterOrderHelper
{
@@ -46,10 +51,6 @@ final class FilterOrderHelper
*/
private array $entityChoices = [];
/**
* @var array<string, array{label: string, options: array}>
*/
private array $userPickers = [];
public function __construct(
private readonly FormFactoryInterface $formFactory,
@@ -79,14 +80,6 @@ final class FilterOrderHelper
return $this->entityChoices;
}
public function addUserPicker(string $name, ?string $label = null, array $options = []): self
{
$this->userPickers[$name] = ['label' => $label, 'options' => $options];
return $this;
}
public function addCheckbox(string $name, array $choices, ?array $default = [], ?array $trans = [], array $options = []): self
{
if ([] === $trans) {
@@ -121,19 +114,6 @@ final class FilterOrderHelper
->handleRequest($this->requestStack->getCurrentRequest());
}
public function getUserPickers(): array
{
return $this->userPickers;
}
/**
* @return list<User>
*/
public function getUserPickerData(string $name): array
{
return $this->getFormData()['user_pickers'][$name];
}
public function hasCheckboxData(string $name): bool
{
return array_key_exists($name, $this->checkboxes);
@@ -223,8 +203,7 @@ final class FilterOrderHelper
'checkboxes' => [],
'dateRanges' => [],
'single_checkboxes' => [],
'entity_choices' => [],
'user_pickers' => []
'entity_choices' => []
];
if ($this->hasSearchBox()) {
@@ -248,10 +227,6 @@ final class FilterOrderHelper
$r['entity_choices'][$name] = ($c['options']['multiple'] ?? true) ? [] : null;
}
foreach ($this->userPickers as $name => $u) {
$r['user_pickers'][$name] = ($u['options']['multiple'] ?? true) ? [] : null;
}
return $r;
}

View File

@@ -39,11 +39,6 @@ class FilterOrderHelperBuilder
*/
private array $entityChoices = [];
/**
* @var array<string, array{label: string, options: array}>
*/
private array $userPickers = [];
public function __construct(
FormFactoryInterface $formFactory,
RequestStack $requestStack,
@@ -90,13 +85,6 @@ class FilterOrderHelperBuilder
return $this;
}
public function addUserPicker(string $name, ?string $label = null, ?array $options = []): self
{
$this->userPickers[$name] = ['label' => $label, 'options' => $options];
return $this;
}
public function build(): FilterOrderHelper
{
$helper = new FilterOrderHelper(
@@ -138,17 +126,6 @@ class FilterOrderHelperBuilder
$helper->addDateRange($name, $label, $from, $to);
}
foreach (
$this->userPickers as $name => [
'label' => $label,
'options' => $options
]
) {
$helper->addUserPicker($name, $label, $options);
}
return $helper;
}
}

View File

@@ -18,5 +18,4 @@ enum FilterOrderPositionEnum: string
case DateRange = 'date_range';
case EntityChoice = 'entity_choice';
case SingleCheckbox = 'single_checkbox';
case UserPicker = 'user_picker';
}

View File

@@ -1,87 +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 Cron;
use Chill\MainBundle\Cron\CronJobInterface;
use Chill\MainBundle\Cron\CronManager;
use Chill\MainBundle\Entity\CronJobExecution;
use Chill\MainBundle\Repository\CronJobExecutionRepository;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
class CronJobDatabaseInteractionTest extends KernelTestCase
{
use ProphecyTrait;
private EntityManagerInterface $entityManager;
private CronJobExecutionRepository $cronJobExecutionRepository;
protected function setUp(): void
{
self::bootKernel();
$this->entityManager = self::$container->get(EntityManagerInterface::class);
$this->cronJobExecutionRepository = self::$container->get(CronJobExecutionRepository::class);
}
public function testCompleteLifeCycle(): void
{
$cronjob = $this->prophesize(CronJobInterface::class);
$cronjob->canRun(null)->willReturn(true);
$cronjob->canRun(Argument::type(CronJobExecution::class))->willReturn(true);
$cronjob->getKey()->willReturn('test-with-data');
$cronjob->run([])->willReturn(['test' => 'execution-0']);
$cronjob->run(['test' => 'execution-0'])->willReturn(['test' => 'execution-1']);
$cronjob->run([])->shouldBeCalledOnce();
$cronjob->run(['test' => 'execution-0'])->shouldBeCalledOnce();
$manager = new CronManager(
$this->cronJobExecutionRepository,
$this->entityManager,
[$cronjob->reveal()],
new NullLogger()
);
// run a first time
$manager->run();
// run a second time
$manager->run();
}
}
class JobWithReturn implements CronJobInterface
{
public function canRun(?CronJobExecution $cronJobExecution): bool
{
return true;
}
public function getKey(): string
{
return 'with-data';
}
public function run(array $lastExecutionData): null|array
{
return ['data' => 'test'];
}
}

View File

@@ -40,7 +40,7 @@ final class CronManagerTest extends TestCase
$jobToExecute = $this->prophesize(CronJobInterface::class);
$jobToExecute->getKey()->willReturn('to-exec');
$jobToExecute->canRun(Argument::type(CronJobExecution::class))->willReturn(true);
$jobToExecute->run([])->shouldBeCalled();
$jobToExecute->run()->shouldBeCalled();
$executions = [
['key' => $jobOld1->getKey(), 'lastStart' => new DateTimeImmutable('yesterday'), 'lastEnd' => new DateTimeImmutable('1 hours ago'), 'lastStatus' => CronJobExecution::SUCCESS],
@@ -64,7 +64,7 @@ final class CronManagerTest extends TestCase
$jobAlreadyExecuted = new JobCanRun('k');
$jobNeverExecuted = $this->prophesize(CronJobInterface::class);
$jobNeverExecuted->getKey()->willReturn('never-executed');
$jobNeverExecuted->run([])->shouldBeCalled();
$jobNeverExecuted->run()->shouldBeCalled();
$jobNeverExecuted->canRun(null)->willReturn(true);
$executions = [
@@ -86,7 +86,7 @@ final class CronManagerTest extends TestCase
$jobAlreadyExecuted = new JobCanRun('k');
$jobNeverExecuted = $this->prophesize(CronJobInterface::class);
$jobNeverExecuted->getKey()->willReturn('never-executed');
$jobNeverExecuted->run([])->shouldBeCalled();
$jobNeverExecuted->run()->shouldBeCalled();
$jobNeverExecuted->canRun(null)->willReturn(true);
$executions = [
@@ -178,9 +178,8 @@ class JobCanRun implements CronJobInterface
return $this->key;
}
public function run(array $lastExecutionData): null|array
public function run(): void
{
return null;
}
}
@@ -196,8 +195,7 @@ class JobCannotRun implements CronJobInterface
return 'job-b';
}
public function run(array $lastExecutionData): null|array
public function run(): void
{
return null;
}
}

View File

@@ -1,68 +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 Services\AddressGeographicalUnit;
use Chill\MainBundle\Entity\CronJobExecution;
use Chill\MainBundle\Service\AddressGeographicalUnit\CollateAddressWithReferenceOrPostalCodeCronJob;
use Chill\MainBundle\Service\AddressGeographicalUnit\CollateAddressWithReferenceOrPostalCodeInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\MockClock;
/**
* @internal
* @coversNothing
*/
class CollateAddressWithReferenceOrPostalCodeCronJobTest extends TestCase
{
use ProphecyTrait;
/**
* @dataProvider provideDataCanRun
*/
public function testCanRun(\DateTimeImmutable $now, ?\DateTimeImmutable $lastExecution, bool $expected): void
{
$execution = match ($lastExecution) {
null => null,
default => (new CronJobExecution('collate-address'))->setLastStart($lastExecution),
};
$clock = new MockClock($now);
$collator = $this->prophesize(CollateAddressWithReferenceOrPostalCodeInterface::class);
$job = new CollateAddressWithReferenceOrPostalCodeCronJob($clock, $collator->reveal());
self::assertEquals($expected, $job->canRun($execution));
}
public function testRun(): void
{
$clock = new MockClock();
$collator = $this->prophesize(CollateAddressWithReferenceOrPostalCodeInterface::class);
$collator->__invoke(0)->shouldBeCalledOnce();
$collator->__invoke(0)->willReturn(1);
$job = new CollateAddressWithReferenceOrPostalCodeCronJob($clock, $collator->reveal());
$actual = $job->run(['last-max-id' => 0]);
self::assertEquals(['last-max-id' => 1], $actual);
}
public static function provideDataCanRun(): iterable
{
yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-10T11:00:00'), false];
yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-10T05:00:00'), true];
yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-01T12:00:00'), true];
yield [new \DateTimeImmutable('2023-07-10T12:00:00'), null, true];
}
}

View File

@@ -1,44 +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 Services\AddressGeographicalUnit;
use Chill\MainBundle\Service\AddressGeographicalUnit\CollateAddressWithReferenceOrPostalCode;
use Doctrine\DBAL\Connection;
use Psr\Log\NullLogger;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
class CollateAddressWithReferenceOrPostalCodeTest extends KernelTestCase
{
private Connection $connection;
protected function setUp(): void
{
self::bootKernel();
$this->connection = self::$container->get(Connection::class);
}
public function testRun(): void
{
$collator = new CollateAddressWithReferenceOrPostalCode(
$this->connection,
new NullLogger()
);
$result = $collator(0);
self::assertGreaterThan(0, $result);
}
}

View File

@@ -1,33 +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\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20230711152947 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add data to ';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_cronjob_execution ADD lastExecutionData JSONB DEFAULT \'{}\'::jsonb NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_cronjob_execution DROP COLUMN lastExecutionData');
}
}

View File

@@ -39,10 +39,8 @@ readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface
return 'accompanying-period-step-change';
}
public function run(array $lastExecutionData): null|array
public function run(): void
{
($this->requestor)();
return null;
}
}

View File

@@ -11,39 +11,45 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class AccompanyingCourseWorkController extends AbstractController
class AccompanyingCourseWorkController extends AbstractController
{
private LoggerInterface $chillLogger;
private PaginatorFactory $paginator;
private SerializerInterface $serializer;
private TranslatorInterface $trans;
private AccompanyingPeriodWorkRepository $workRepository;
public function __construct(
private readonly TranslatorInterface $trans,
private readonly SerializerInterface $serializer,
private readonly AccompanyingPeriodWorkRepository $workRepository,
private readonly PaginatorFactory $paginator,
private readonly LoggerInterface $chillLogger,
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory
TranslatorInterface $trans,
SerializerInterface $serializer,
AccompanyingPeriodWorkRepository $workRepository,
PaginatorFactory $paginator,
LoggerInterface $chillLogger
) {
$this->trans = $trans;
$this->serializer = $serializer;
$this->workRepository = $workRepository;
$this->paginator = $paginator;
$this->chillLogger = $chillLogger;
}
/**
@@ -156,21 +162,11 @@ final class AccompanyingCourseWorkController extends AbstractController
{
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::SEE, $period);
$filter = $this->buildFilterOrder($period);
$filterData = [
'types' => $filter->hasEntityChoice('typesFilter') ? $filter->getEntityChoiceData('typesFilter') : [],
'before' => $filter->getDateRangeData('dateFilter')['to'],
'after' => $filter->getDateRangeData('dateFilter')['from'],
'user' => $filter->getUserPickerData('userFilter')
];
$totalItems = $this->workRepository->countByAccompanyingPeriod($period);
$paginator = $this->paginator->create($totalItems);
$works = $this->workRepository->findByAccompanyingPeriodOpenFirst(
$period,
$filterData,
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
@@ -179,7 +175,6 @@ final class AccompanyingCourseWorkController extends AbstractController
'accompanyingCourse' => $period,
'works' => $works,
'paginator' => $paginator,
'filter' => $filter
]);
}
@@ -204,7 +199,7 @@ final class AccompanyingCourseWorkController extends AbstractController
]);
}
private function createDeleteForm(int $id): FormInterface
private function createDeleteForm(int $id): Form
{
$params = [];
$params['id'] = $id;
@@ -215,26 +210,4 @@ final class AccompanyingCourseWorkController extends AbstractController
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}
private function buildFilterOrder($associatedPeriod): FilterOrderHelper
{
$filterBuilder = $this->filterOrderHelperFactory->create(self::class);
$types = $this->workRepository->findActionTypeByPeriod($associatedPeriod);
$filterBuilder
->addDateRange('dateFilter', 'accompanying_course_work.date_filter');
if (1 < count($types)) {
$filterBuilder
->addEntityChoice('typesFilter', 'accompanying_course_work.types_filter', \Chill\PersonBundle\Entity\SocialWork\SocialAction::class, $types, [
'choice_label' => fn (SocialAction $sa) => $this->translatableStringHelper->localize($sa->getTitle())
]);
}
$filterBuilder
->addUserPicker('userFilter', 'accompanying_course_work.user_filter', ['required' => false])
;
return $filterBuilder->build();
}
}

View File

@@ -1,46 +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\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
class AccompanyingCourseWorkEvaluationDocumentController extends AbstractController
{
public function __construct(private Security $security)
{
}
/**
* @Route(
* "{_locale}/person/accompanying-period/work/evaluation/document/{id}/show",
* name="chill_person_accompanying_period_work_evaluation_document_show",
* methods={"GET"}
* )
*/
public function showAccompanyingCourseWork(AccompanyingPeriodWorkEvaluationDocument $document): Response
{
$work = $document->getAccompanyingPeriodWorkEvaluation()->getAccompanyingPeriodWork();
return $this->redirectToRoute(
$this->security->isGranted(AccompanyingPeriodWorkVoter::UPDATE, $work) ?
'chill_person_accompanying_period_work_edit' : 'chill_person_accompanying_period_work_show',
[
'id' => $work->getId()
]
);
}
}

View File

@@ -95,103 +95,29 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository
* * then, closed works
*
* @return AccompanyingPeriodWork[]
* @param array{types?: list<SocialAction>, user?: list<User>, after?: null|\DateTimeImmutable, before?: null|\DateTimeImmutable} $filters
*/
public function findByAccompanyingPeriodOpenFirst(AccompanyingPeriod $period, array $filters, int $limit = 10, int $offset = 0): array
public function findByAccompanyingPeriodOpenFirst(AccompanyingPeriod $period, int $limit = 10, int $offset = 0): array
{
$rsm = new ResultSetMappingBuilder($this->em);
$rsm->addRootEntityFromClassMetadata(AccompanyingPeriodWork::class, 'w');
$sql = "SELECT {$rsm} FROM chill_person_accompanying_period_work w
LEFT JOIN chill_person_accompanying_period_work_referrer AS rw ON accompanyingperiodwork_id = w.id
WHERE accompanyingPeriod_id = :periodId";
// implement filters
if ([] !== ($filters['types'] ?? [])) {
$sql .= " AND w.socialaction_id IN (:types)";
}
if ([] !== ($filters['user'] ?? [])) {
$sql .= " AND rw.user_id IN ("
. implode(
', ',
// we add a user_xx for each key of the 'user' list
array_map(fn (User $u, int $idx) => ':user_' . $idx, $filters['user'], array_keys($filters['user']))
)
. ")";
}
$sql .= " AND daterange(:after::date, :before::date) && daterange(w.startDate, w.endDate)";
// if the start and end date were inversed, we inverse the order to avoid an error
if (null !== ($filters['after'] ?? null) && null !== ($filters['before']) && $filters['after'] > $filters['before']) {
$before = $filters['after'];
$after = $filters['before'];
} else {
$before = $filters['before'];
$after = $filters['after'];
}
// set limit and offset
$sql .= " ORDER BY
CASE WHEN enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC,
startdate DESC,
enddate DESC,
id DESC";
$sql .= " LIMIT :limit OFFSET :offset";
$typeIds = [];
foreach ($filters['types'] as $type) {
$typeIds[] = $type->getId();
}
WHERE accompanyingPeriod_id = :periodId
ORDER BY
CASE WHEN enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC,
startdate DESC,
enddate DESC,
id DESC
LIMIT :limit OFFSET :offset";
$nq = $this->em->createNativeQuery($sql, $rsm)
->setParameter('periodId', $period->getId(), Types::INTEGER)
->setParameter('types', $typeIds)
->setParameter('after', $after)
->setParameter('before', $before)
->setParameter('limit', $limit, Types::INTEGER)
->setParameter('offset', $offset, Types::INTEGER);
foreach ($filters['user'] as $key => $user) {
$nq->setParameter('user_' . $key, $user);
}
return $nq->getResult();
}
/**
* Return a list of types of social actions associated to the accompanying period
*
* @return array<SocialAction>
*/
public function findActionTypeByPeriod(AccompanyingPeriod $period): array
{
$in = $this->em->createQueryBuilder();
$in
->select('1')
->from(AccompanyingPeriodWork::class, 'apw');
$in->andWhere('apw.accompanyingPeriod = :period')->setParameter('period', $period);
// join between the embedded exist query and the main query
$in->andWhere('apw.socialAction = sa');
$qb = $this->em->createQueryBuilder()->setParameters($in->getParameters());
$qb
->select('sa')
->from(SocialAction::class, 'sa')
->where(
$qb->expr()->exists($in->getDQL())
);
return $qb->getQuery()->getResult();
}
public function findNearEndDateByUser(User $user, DateTimeImmutable $since, DateTimeImmutable $until, int $limit = 20, int $offset = 0): array
{
return $this->buildQueryNearEndDateByUser($user, $since, $until)

View File

@@ -68,45 +68,27 @@
<ul class="list-content fa-ul">
<li v-if="person.current_household_id">
<i class="fa fa-li fa-map-marker"></i>
<address-render-box v-if="person.current_household_address" :address="person.current_household_address" :isMultiline="isMultiline"></address-render-box>
<p v-else class="chill-no-data-statement">{{ $t('renderbox.household_without_address') }}</p>
<address-render-box v-if="person.current_household_address"
:address="person.current_household_address"
:isMultiline="isMultiline">
</address-render-box>
<p v-else class="chill-no-data-statement">
{{ $t('renderbox.household_without_address') }}
</p>
<a v-if="options.addHouseholdLink === true"
:href="getCurrentHouseholdUrl"
:title="$t('persons_associated.show_household_number', {id: person.current_household_id})">
<span class="badge rounded-pill bg-chill-beige">
<i class="fa fa-fw fa-home"></i><!--{{ $t('persons_associated.show_household') }}-->
</span>
<span class="badge rounded-pill bg-chill-beige">
<i class="fa fa-fw fa-home"></i><!--{{ $t('persons_associated.show_household') }}-->
</span>
</a>
</li>
<li v-else-if="options.addNoData">
<i class="fa fa-li fa-map-marker"></i><p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
<i class="fa fa-li fa-map-marker"></i>
<p class="chill-no-data-statement">
{{ $t('renderbox.no_data') }}
</p>
</li>
<template v-if="this.showResidentialAddresses && (person.current_residential_addresses || []).length > 0">
<li v-for="(addr, i) in person.current_residential_addresses" :key="i">
<i class="fa fa-li fa-map-marker"></i>
<div v-if="addr.address">
<span class="item-key">{{ $t('renderbox.residential_address') }}:</span>
<div style="margin-top: -1em;">
<address-render-box :address="addr.address" :isMultiline="isMultiline"></address-render-box>
</div>
</div>
<div v-else-if="addr.hostPerson" class="mt-3">
<p>{{ $t('renderbox.located_at') }}:</p>
<span class="chill-entity entity-person badge-person">
<person-text v-if="addr.hostPerson" :person="addr.hostPerson"></person-text>
</span>
<address-render-box v-if="addr.hostPerson.address" :address="addr.hostPerson.address" :isMultiline="isMultiline"></address-render-box>
</div>
<div v-else-if="addr.hostThirdParty" class="mt-3">
<p>{{ $t('renderbox.located_at') }}:</p>
<span class="chill-entity entity-person badge-thirdparty">
<third-party-text v-if="addr.hostThirdParty" :thirdparty="addr.hostThirdParty"></third-party-text>
</span>
<address-render-box v-if="addr.hostThirdParty.address" :address="addr.hostThirdParty.address" :isMultiline="isMultiline"></address-render-box>
</div>
</li>
</template>
<li v-if="person.email">
<i class="fa fa-li fa-envelope-o"></i>
@@ -149,6 +131,53 @@
</div>
</div>
<div class="item-col mx-3"
v-if="this.showResidentialAddresses && (person.current_residential_addresses || []).length > 0">
<div class="float-button bottom">
<div class="box">
<ul class="list-content fa-ul">
<li v-for="(addr, i) in person.current_residential_addresses" :key="i">
<i class="fa fa-li fa-map-marker"></i>
<div v-if="addr.address">
<address-render-box
:address="addr.address"
:isMultiline="isMultiline">
</address-render-box>
<p>({{ $t('renderbox.residential_address') }})</p>
</div>
<div v-else-if="addr.hostPerson" class="mt-3">
<p>{{ $t('renderbox.located_at') }}:</p>
<span class="chill-entity entity-person badge-person">
<person-text
v-if="addr.hostPerson"
:person="addr.hostPerson"
></person-text>
</span>
<address-render-box v-if="addr.hostPerson.address"
:address="addr.hostPerson.address"
:isMultiline="isMultiline">
</address-render-box>
</div>
<div v-else-if="addr.hostThirdParty" class="mt-3">
<p>{{ $t('renderbox.located_at') }}:</p>
<span class="chill-entity entity-person badge-thirdparty">
<third-party-text
v-if="addr.hostThirdParty"
:thirdparty="addr.hostThirdParty"
></third-party-text>
</span>
<address-render-box v-if="addr.hostThirdParty.address"
:address="addr.hostThirdParty.address"
:isMultiline="isMultiline">
</address-render-box>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<slot name="end-bloc"></slot>

View File

@@ -5,23 +5,18 @@
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block content %}
<div class="accompanying-course-work">
<h1>{{ block('title') }}</h1>
{{ filter|chill_render_filter_order_helper }}
{% if works|length == 0 %}
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}</p>
{% else %}

View File

@@ -59,7 +59,7 @@
<span class=" d-block d-sm-inline-block">
{{ address|chill_entity_render_box({
'render': 'inline', 'multiline': false, 'with_picto': true, 'with_delimiter': true,
'details_button': true
'details_button': false
}) }}
</span>
{% endif %}

View File

@@ -56,12 +56,13 @@
{%- if address is not null -%}
{{ address|chill_entity_render_box({
'render': 'inline', 'multiline': false, 'with_picto': true, 'with_delimiter': true,
'details_button': true
'details_button': false
}) }}
{%- endif -%}
{% if person.getCurrentHousehold is not null %}
<a href="{{ chill_path_add_return_path('chill_person_household_summary', { 'household_id' : person.getCurrentHousehold.id } ) }}"
class="btn btn-sm household-link" title="{{ 'Show household'|trans }}">
<a class="btn household-link text-end"
href="{{ chill_path_add_return_path('chill_person_household_summary', { 'household_id' : person.getCurrentHousehold.id } ) }}"
title="{{ 'Show household'|trans }}">
<i class="fa fa-lg fa-home"></i>
</a>
{% endif %}

View File

@@ -47,7 +47,8 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{
return [
'persons' => $this->getRelatedEntity($entityWorkflow)?->getPersons() ?? [],
'persons' => $this->getRelatedEntity($entityWorkflow)
->getPersons(),
];
}

View File

@@ -913,9 +913,6 @@ accompanying_course_work:
social_evaluation: Évaluation
private_comment: Commentaire privé
timeSpent: Temps de rédaction
date_filter: Filtrer par date
types_filter: Filtrer par type d'action
user_filter: Filtrer par intervenant
#

View File

@@ -26,7 +26,6 @@ use Chill\TaskBundle\Event\TaskEvent;
use Chill\TaskBundle\Event\UI\UIEvent;
use Chill\TaskBundle\Form\SingleTaskType;
use Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface;
use Chill\TaskBundle\Repository\SingleTaskStateRepository;
use Chill\TaskBundle\Security\Authorization\TaskVoter;
use LogicException;
use Psr\Log\LoggerInterface;
@@ -72,8 +71,7 @@ final class SingleTaskController extends AbstractController
EventDispatcherInterface $eventDispatcher,
TimelineBuilder $timelineBuilder,
LoggerInterface $logger,
FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
private SingleTaskStateRepository $singleTaskStateRepository
FilterOrderHelperFactoryInterface $filterOrderHelperFactory
) {
$this->eventDispatcher = $eventDispatcher;
$this->timelineBuilder = $timelineBuilder;
@@ -301,17 +299,13 @@ final class SingleTaskController extends AbstractController
$this->denyAccessUnlessGranted(TaskVoter::SHOW, null);
$filterOrder = $this->buildFilterOrder();
$filteredUsers = $filterOrder->getUserPickerData('userPicker');
$flags = array_merge(
$filterOrder->getCheckboxData('status'),
array_map(static fn ($i) => 'state_' . $i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states'))
);
$nb = $this->singleTaskAclAwareRepository->countByAllViewable(
$filterOrder->getQueryString(),
$flags,
$filteredUsers
$flags
);
$paginator = $this->paginatorFactory->create($nb);
@@ -319,7 +313,6 @@ final class SingleTaskController extends AbstractController
$tasks = $this->singleTaskAclAwareRepository->findByAllViewable(
$filterOrder->getQueryString(),
$flags,
$filteredUsers,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage(),
[
@@ -353,7 +346,7 @@ final class SingleTaskController extends AbstractController
$filterOrder = $this->buildFilterOrder();
$flags = array_merge(
$filterOrder->getCheckboxData('status'),
array_map(static fn ($i) => 'state_' . $i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states'))
);
$nb = $this->singleTaskAclAwareRepository->countByCourse(
$course,
@@ -402,7 +395,7 @@ final class SingleTaskController extends AbstractController
$filterOrder = $this->buildFilterOrder();
$flags = array_merge(
$filterOrder->getCheckboxData('status'),
array_map(static fn ($i) => 'state_' . $i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states'))
);
$nb = $this->singleTaskAclAwareRepository->countByPerson(
$person,
@@ -454,10 +447,10 @@ final class SingleTaskController extends AbstractController
{
$this->denyAccessUnlessGranted('ROLE_USER');
$filterOrder = $this->buildFilterOrder(false);
$filterOrder = $this->buildFilterOrder();
$flags = array_merge(
$filterOrder->getCheckboxData('status'),
array_map(static fn ($i) => 'state_' . $i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states'))
);
$nb = $this->singleTaskAclAwareRepository->countByCurrentUsersTasks(
$filterOrder->getQueryString(),
@@ -669,7 +662,7 @@ final class SingleTaskController extends AbstractController
return $form;
}
private function buildFilterOrder($includeFilterByUser = true): FilterOrderHelper
private function buildFilterOrder(): FilterOrderHelper
{
$statuses = ['no-alert', 'warning', 'alert'];
$statusTrans = [
@@ -677,26 +670,17 @@ final class SingleTaskController extends AbstractController
'Tasks near deadline',
'Tasks over deadline',
];
$states = [
// todo: get a list of possible states dynamically
'new', 'in_progress', 'closed', 'canceled',
];
$filterBuilder = $this->filterOrderHelperFactory
return $this->filterOrderHelperFactory
->create(self::class)
->addSearchBox()
->addCheckbox('status', $statuses, $statuses, $statusTrans);
$states = $this->singleTaskStateRepository->findAllExistingStates();
$checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['closed', 'canceled', 'validated'], true)));
if ([] !== $states) {
$filterBuilder
->addCheckbox('states', $states, $checked);
}
if ($includeFilterByUser) {
$filterBuilder
->addUserPicker('userPicker', 'Filter by user', ['multiple' => true, 'required' => false]);
}
return $filterBuilder->build();
->addCheckbox('status', $statuses, $statuses, $statusTrans)
->addCheckbox('states', $states, ['new', 'in_progress'])
->build();
}
/**

View File

@@ -51,37 +51,18 @@ final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepository
public function buildBaseQuery(
?string $pattern = null,
?array $flags = [],
?array $users = []
?array $flags = []
): QueryBuilder {
$qb = $this->em->createQueryBuilder();
$qb
->from(SingleTask::class, 't');
if (null !== $pattern && '' !== $pattern) {
if (!empty($pattern)) {
$qb->andWhere($qb->expr()->like('LOWER(UNACCENT(t.title))', 'LOWER(UNACCENT(:pattern))'))
->setParameter('pattern', '%' . $pattern . '%');
}
if (null !== $users && count($users) > 0) {
$orXUser = $qb->expr()->orX();
foreach ($users as $key => $user) {
$orXUser->add(
$qb->expr()->eq('t.assignee', ':user_' . $key)
);
$qb->setParameter('user_' . $key, $user);
}
if ($orXUser->count() > 0) {
$qb->andWhere($orXUser);
}
return $qb;
}
if (null !== $flags && count($flags) > 0) {
if (count($flags) > 0) {
$orXDate = $qb->expr()->orX();
$orXState = $qb->expr()->orX();
$now = new DateTime();
@@ -202,10 +183,9 @@ final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepository
public function countByAllViewable(
?string $pattern = null,
?array $flags = [],
?array $users = []
?array $flags = []
): int {
$qb = $this->buildBaseQuery($pattern, $flags, $users);
$qb = $this->buildBaseQuery($pattern, $flags);
return $this
->addACLGlobal($qb)
@@ -251,12 +231,11 @@ final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepository
public function findByAllViewable(
?string $pattern = null,
?array $flags = [],
?array $users = [],
?int $start = 0,
?int $limit = 50,
?array $orderBy = []
): array {
$qb = $this->buildBaseQuery($pattern, $flags, $users);
$qb = $this->buildBaseQuery($pattern, $flags);
$qb = $this->addACLGlobal($qb);
return $this->getResult($qb, $start, $limit, $orderBy);

View File

@@ -18,8 +18,7 @@ interface SingleTaskAclAwareRepositoryInterface
{
public function countByAllViewable(
?string $pattern = null,
?array $flags = [],
?array $users = []
?array $flags = []
): int;
public function countByCourse(
@@ -39,7 +38,6 @@ interface SingleTaskAclAwareRepositoryInterface
public function findByAllViewable(
?string $pattern = null,
?array $flags = [],
?array $users = [],
?int $start = 0,
?int $limit = 50,
?array $orderBy = []

View File

@@ -1,47 +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\TaskBundle\Repository;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
class SingleTaskStateRepository
{
private const FIND_ALL_STATES = <<<'SQL'
SELECT DISTINCT jsonb_array_elements_text(current_states) FROM chill_task.single_task
SQL;
public function __construct(
private Connection $connection
) {
}
/**
* Return a list of all states associated to at least one single task in the database
*
* @return list<string>
* @throws Exception
*/
public function findAllExistingStates(): array
{
$states = [];
foreach ($this->connection->fetchAllNumeric(self::FIND_ALL_STATES) as $row) {
if ('' !== $row[0] && null !== $row[0]) {
$states[] = $row[0];
}
}
return $states;
}
}

View File

@@ -27,10 +27,8 @@
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('page_task_list') }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('page_task_list') }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}

View File

@@ -1,8 +1,4 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill_task.single_task_repository:
class: Chill\TaskBundle\Repository\SingleTaskRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
@@ -14,8 +10,8 @@ services:
- "@chill.main.security.authorization.helper"
Chill\TaskBundle\Repository\SingleTaskRepository: '@chill_task.single_task_repository'
Chill\TaskBundle\Repository\SingleTaskAclAwareRepository: ~
Chill\TaskBundle\Repository\SingleTaskAclAwareRepository:
autowire: true
autoconfigure: true
Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface: '@Chill\TaskBundle\Repository\SingleTaskAclAwareRepository'
Chill\TaskBundle\Repository\SingleTaskStateRepository: ~

View File

@@ -65,7 +65,6 @@ Not assigned: Aucun utilisateur assigné
For person: Pour
By: Par
Any tasks: Aucune tâche
Filter by user: Filtrer par utilisateur(s)
# transitions - default task definition
"new": "nouvelle"

View File

@@ -71,9 +71,12 @@ class ThirdPartyApiSearch implements SearchApiInterface
->setSelectKey('tparty')
->setSelectJsonbMetadata("jsonb_build_object('id', tparty.id)")
->setFromClause('chill_3party.third_party AS tparty
LEFT JOIN chill_main_address cma ON cma.id = tparty.address_id
LEFT JOIN chill_main_postal_code cmpc ON cma.postcode_id = cmpc.id
LEFT JOIN chill_3party.third_party AS parent ON tparty.parent_id = parent.id
LEFT JOIN chill_main_address cma ON cma.id = COALESCE(parent.address_id, tparty.address_id)
LEFT JOIN chill_main_postal_code cmpc ON cma.postcode_id = cmpc.id');
LEFT JOIN chill_main_address cma_p ON parent.address_id = cma_p.id
LEFT JOIN chill_main_postal_code cmpc_p ON cma_p.postcode_id = cmpc.id')
->andWhereClause('tparty.active IS TRUE');
$strs = explode(' ', $pattern);
$wheres = [];
@@ -99,8 +102,9 @@ class ThirdPartyApiSearch implements SearchApiInterface
(parent.canonicalized LIKE '%s' || LOWER(UNACCENT(?)) || '%')::int
) + " .
// take postcode label into account, but lower than the canonicalized field
"COALESCE((LOWER(UNACCENT(cmpc.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0)";
$pertinenceArgs[] = [$str, $str, $str, $str, $str];
"COALESCE((LOWER(UNACCENT(cmpc.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0) + " .
"COALESCE((LOWER(UNACCENT(cmpc_p.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0)";
$pertinenceArgs[] = [$str, $str, $str, $str, $str, $str];
}
}