Compare commits

..

156 Commits

Author SHA1 Message Date
b5c9e65986 Merge branch 'refs/heads/master' into 321-text-editor
# Conflicts:
#	src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts
#	src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue
#	yarn.lock
2025-05-21 20:03:14 +02:00
8b2af35e97 Fix typo 2025-05-21 17:57:35 +02:00
dc44c46667 Fix SocialActionCSVExporterTest.php 2025-05-21 09:29:25 +02:00
ba571c1a69 Merge branch 'master' of https://gitlab.com/Chill-Projet/chill-bundles 2025-05-20 10:04:53 +02:00
6a364705f2 Git add desactivation date for social issue csv export 2025-05-20 09:57:07 +02:00
b6d454691a Git add desactivation date for social action csv export 2025-05-20 09:38:39 +02:00
6d7a6932a9 Merge branch '365-correct-activities-works-counters' into 'master'
#365 correct works counter in acc course summary

Closes #365

See merge request Chill-Projet/chill-bundles!826
2025-05-16 14:40:20 +00:00
juminet
2faf194b15 #365 correct works counter in acc course summary 2025-05-16 14:40:19 +00:00
f207599d86 Merge branch '352-remove-wopi-link-module' into 'master'
Resolve "Code mort: module wopi-link semble inutilisé"

Closes #352

See merge request Chill-Projet/chill-bundles!824
2025-05-16 08:33:57 +00:00
b0959f8cc5 Resolve "Code mort: module wopi-link semble inutilisé" 2025-05-16 08:33:56 +00:00
4c5dee5f0a Fix pipeline 2025-05-14 17:31:48 +02:00
f6c98aa0d5 Add missing translation for user_group.no_user_groups 2025-05-14 14:53:50 +02:00
6d13d184d5 Merge branch 'master' of https://gitlab.com/Chill-Projet/chill-bundles 2025-05-14 13:57:22 +02:00
af36eccfaf Allow more characters for maritalstatus id 2025-05-14 13:56:36 +02:00
483a20a43f Merge branch '374-task-default-filters' into 'master'
Resolve "Module tâche: enlever les filtres par défaut"

Closes #374

See merge request Chill-Projet/chill-bundles!819
2025-05-13 08:59:01 +00:00
6d8e2ad825 Resolve "Module tâche: enlever les filtres par défaut" 2025-05-13 08:59:01 +00:00
86388a63a8 Merge branch '381-uncoherent-display-persons-acc-course-works' into 'master'
381 display previous person participation in acc course work

Closes #381

See merge request Chill-Projet/chill-bundles!823
2025-05-13 08:45:38 +00:00
juminet
5ea55ebfe5 381 display previous person participation in acc course work 2025-05-13 08:45:38 +00:00
f97dc8f931 Merge branch '377-document-file-name' into 'master'
377 - add the document filename to document title when uploading a new document

Closes #377

See merge request Chill-Projet/chill-bundles!821
2025-05-13 08:30:31 +00:00
juminet
a9c3aab528 377 - add the document filename to document title when uploading a new document 2025-05-13 08:30:30 +00:00
1181377bd6 Merge branch '376-typeerror-doc-history' into 'master'
#376 prevent typerror in doc-history + improve display of document history

Closes #376

See merge request Chill-Projet/chill-bundles!820
2025-05-13 07:50:53 +00:00
juminet
2275b7c560 #376 prevent typerror in doc-history + improve display of document history 2025-05-13 07:50:52 +00:00
4a8d298ae5 Fix typing error for the display of text in calendar events 2025-05-05 10:42:04 +02:00
3e7f03d331 Add possibility to cancel a workflow if there is a pending signature [ci-skip] 2025-04-17 14:23:32 +02:00
b830952b9e Release v3.11.0 2025-04-17 14:22:35 +02:00
ad17313c61 Merge branch '380-remove-not-null-constraint-household-composition' into 'master'
Remove "not null" validation on HouseholdComposition properties

Closes #380

See merge request Chill-Projet/chill-bundles!818
2025-04-17 09:03:15 +00:00
620515ad15 Remove "not null" validation on HouseholdComposition properties
This change removes the "not null" constraint on specific properties in the HouseholdComposition entity to allow null values. The adjustment addresses Issue #380 and ensures better flexibility without impacting the schema.
2025-04-17 10:56:45 +02:00
50c377ee22 Merge branch 'fix/fix-stored-object-version-not-delete-when-not-exists-on-disk' into 'master'
Fix error when cleaning non-existent stored object versions

See merge request Chill-Projet/chill-bundles!816
2025-04-17 08:36:36 +00:00
cc7e7a90ee Merge branch 'fix/cancel-stale-workflow-handle-fails-silently' into 'master'
Add consistent LOG_PREFIX and key to CancelStaleWorkflowHandler logs

See merge request Chill-Projet/chill-bundles!817
2025-04-16 19:10:37 +00:00
1d4ef19051 Add key to log messages context and add log prefix to enhance CancelStaleWorkflowHandler messages
Introduced a consistent LOG_PREFIX to all log messages in CancelStaleWorkflowHandler. This ensures clearer contextual identification in logs and improves traceability when debugging or monitoring workflow actions.
2025-04-16 21:03:29 +02:00
8337a724d1 Fix error when cleaning non-existent stored object versions
Prevent the `RemoveOldVersionMessageHandler` from throwing errors when the stored object version is missing on disk. Introduced a check to log a notice instead of attempting deletion in such cases and added corresponding test coverage.
2025-04-16 16:26:02 +02:00
8ca377d5d4 Merge branch 'button-signature-zone' into 'master'
Add button unique signature zone

See merge request Chill-Projet/chill-bundles!812
2025-04-15 13:09:54 +00:00
224e0bae43 Add button unique signature zone 2025-04-15 13:09:54 +00:00
3aa4fac80d Merge branch '364-tel2-third-party' into 'master'
Adding a second phone number to thirdparty entity

Closes #364

See merge request Chill-Projet/chill-bundles!810
2025-04-15 12:59:58 +00:00
juminet
a7517eb647 Adding a second phone number to thirdparty entity 2025-04-15 12:59:57 +00:00
e278e636e0 Merge branch '365-add-activities-works-counter' into 'master'
#365 Add works and activities counter

Closes #365

See merge request Chill-Projet/chill-bundles!809
2025-04-14 09:34:02 +00:00
juminet
40e373a9c7 #365 Add works and activities counter 2025-04-14 09:34:02 +00:00
1c1f418b18 Merge branch 'fix/acc-period-step-change-shorten-elapsed' into 'master'
Adjust cronjob interval to ensure daily execution

See merge request Chill-Projet/chill-bundles!814
2025-04-10 13:49:11 +00:00
bf0e14b43a Merge branch '102-liste-des-document-titre-long' into 'master'
Fix graphical bug in document list with title overflowing the frame, and add new classes to display title and aside in flex-table (DX + Fix)

Closes #102 and #22

See merge request Chill-Projet/chill-bundles!815
2025-04-10 13:47:40 +00:00
203a098054 Refactor document row layouts to use CSS grid
Replaced the old 'item-col' structure with a 'item-two-col-grid' layout across multiple templates, improving consistency and responsiveness. Introduced CSS grid styles ensuring proper alignment and wrapping of titles and aside elements in different viewport sizes. This enhances the overall readability and maintainability of the views.
2025-04-10 15:41:06 +02:00
d58acff541 Add css layout for badges for accompanying period work, activity and calendar 2025-04-10 15:41:06 +02:00
5858e05a42 Replace grid in person_list document to fit the whole width of the page 2025-04-10 15:31:06 +02:00
b9b4fafe14 Adjust cronjob interval to ensure daily execution
The interval for `AccompanyingPeriodStepChangeCronjob` was reduced from 24 hours to 23 hours and 45 minutes. This change guarantees at least one execution per day, addressing potential issues with timing overlaps.
2025-04-09 21:30:41 +02:00
fe6949ea26 Update chill bundles to v3.10.3 2025-03-18 13:47:31 +01:00
8a444a12f4 Turn off eslint error ban-ts-comment 2025-03-18 10:57:24 +01:00
8b7b5ceed7 Release v3.10.2 2025-03-17 22:30:17 +01:00
b0e826d05a Revert "Remove unnecessary ts-expect-error"
This reverts commit 7f101ba616.
2025-03-17 22:22:37 +01:00
6fb9c3af3f Release v3.10.1 2025-03-17 21:59:32 +01:00
7f101ba616 Remove unnecessary ts-expect-error 2025-03-17 21:59:16 +01:00
cea82fac10 Merge branch '333-removing-node-deps-to-symfony-ux-translator' into 'master'
Copy Symfony UX Translator module into to chill-bundles

Closes #333

See merge request Chill-Projet/chill-bundles!808
2025-03-17 20:45:48 +00:00
1344fc33e1 Copy Symfony UX Translator module into to chill-bundles 2025-03-17 20:45:48 +00:00
c8e09a28e6 Eslint fixes 2025-03-17 17:32:28 +01:00
c52d4b2a0e Release v3.10.0 2025-03-17 16:41:07 +01:00
7f326d5441 Fix typing of argument in FullCAlendar slot 2025-03-17 16:29:59 +01:00
2840c06476 Merge branch '368-fix-user-search-engine' into 'master'
Fix search query for user-groups

Closes #368

See merge request Chill-Projet/chill-bundles!806
2025-03-14 14:10:53 +00:00
7ddf84ea5a Fix ts errors upon prod compilation 2025-03-14 15:07:23 +01:00
f202625ea8 Fix LIKE clause logic in UserGroupRepository query
Adjusted the parameter order and ensured consistent use of LOWER and UNACCENT functions in the LIKE clause. This resolves potential mismatches and improves query reliability for pattern matching.
2025-03-14 15:04:22 +01:00
7a9168fcdb Refactor variable declarations in pick-entity module.
Consolidated variable declarations into a single statement using const. This improves code readability and aligns with modern JavaScript best practices.
2025-03-14 15:02:09 +01:00
40eb71f95a Merge branch '362-bug-manager-registry-fix2' into 'master'
Resolve loading of manager registry

Closes #362

See merge request Chill-Projet/chill-bundles!803
2025-03-05 16:45:08 +00:00
84b7cc8145 Php cs fixes 2025-03-05 10:46:36 +01:00
08af530726 Replace deprecated Statement::create() method use constructor directly 2025-03-05 10:26:22 +01:00
03b2496817 Fix dependency injection issues in AbstractCRUDController
Replaced incorrect service definitions in AbstractCRUDController to ensure proper dependency injection. Specifically, fixed retrieval of the ManagerRegistry and Validator services to resolve CalendarRange save errors (Issue #362). No schema changes were introduced.
2025-03-04 23:02:19 +01:00
f638ce71fd Merge branch '363-display_actions_by_issue' into 'master'
Resolve "Change display of social issues and social actions"

Closes #363

See merge request Chill-Projet/chill-bundles!802
2025-03-04 14:40:48 +00:00
b39997f00a Resolve "Change display of social issues and social actions" 2025-03-04 14:40:48 +00:00
38b21a2159 Update chill to version v3.9.2 2025-02-27 16:20:03 +01:00
17db571244 Add changie and linter fixes 2025-02-27 16:19:21 +01:00
8c8c16c1a1 use fetchResult instead of makeFetch for acpw creation 2025-02-27 16:16:54 +01:00
046d3ec9f1 use fetchResult instead of makeFetch for SocialIssuesAcc.vue component 2025-02-27 16:12:00 +01:00
596833f1a5 use fetchResult instead of makeFetch 2025-02-27 15:58:53 +01:00
66e4bab558 Fix pipeline phpunit and lint 2025-02-27 13:50:35 +01:00
d1c9926bb1 Update bundles to version v3.9.1 2025-02-27 12:21:26 +01:00
a71d066765 Fix post/path requests with missing 'type' property for gender 2025-02-27 12:19:10 +01:00
217ac7b9e7 Linter fixes 2025-02-27 11:54:48 +01:00
be901822bc Update chill version to v3.9.0 2025-02-27 11:26:25 +01:00
2dcce7b826 Remove dumps 2025-02-25 15:29:45 +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
dcd1777a70 Pass content of the textEditor to the symfony form 2025-02-17 16:25:17 +01:00
a6eb28175a Revert backend to original state and load commentEditor vue app anytime CommentType form field is used 2025-02-17 14:13:07 +01: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
7d78512823 Change symfony form back to original 2025-02-17 13:11:03 +01: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
d0cd4792d6 Fix dynamicEntityPicker: declare variables 2025-02-13 15:02:06 +01:00
6d196ead94 Merge branch '321-text-editor' of https://gitlab.com/Chill-Projet/chill-bundles into 321-text-editor 2025-02-13 14:49:51 +01:00
4047d5fd5b WIP Allow for comment content to be submitted to backend 2025-02-13 14:49:33 +01:00
9aac80d834 Change logic to allow as many comment vue apps to be charged on same page as needed 2025-02-13 14:49:33 +01:00
7560dc57c6 Remove comment editor component from Activity vue app 2025-02-13 14:49:33 +01:00
10314845f6 Turn component into a small vue app and add public private comment logic 2025-02-13 14:49:33 +01:00
9b84bc4d69 Use update ckeditor5 2025-02-13 14:49:33 +01:00
a2fcf039be Refactor CKEditor integration across application
Replaced `ClassicEditor` with a consistent `classicEditor` and added centralized editor configuration using `editorConfig`. Simplified webpack configuration by removing CKEditor-specific setups and dependencies, improving maintainability.
2025-02-13 14:49:33 +01:00
b4d887a372 upgrade ckeditor 2025-02-13 14:49:32 +01:00
0aaa7122da upgrade ckeditor/vue 2025-02-13 14:49:32 +01:00
1bc7f85874 Implement localStorage and toggling of simple/rich text editor 2025-02-13 14:49:32 +01:00
1d2fd000aa Setup new component for text editor 2025-02-13 14:49:32 +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
506df432b0 WIP Allow for comment content to be submitted to backend 2025-02-06 16:07:40 +01:00
c32c18b0e2 Change logic to allow as many comment vue apps to be charged on same page as needed 2025-02-06 15:10:49 +01:00
321d569ee9 Remove comment editor component from Activity vue app 2025-02-06 14:32:22 +01:00
cd40eb3932 Turn component into a small vue app and add public private comment logic 2025-02-06 12:46:19 +01:00
f0f2531fa3 Use update ckeditor5 2025-02-06 10:33:32 +01:00
183a220e7b Refactor CKEditor integration across application
Replaced `ClassicEditor` with a consistent `classicEditor` and added centralized editor configuration using `editorConfig`. Simplified webpack configuration by removing CKEditor-specific setups and dependencies, improving maintainability.
2025-02-06 10:33:32 +01:00
9df127a82c upgrade ckeditor 2025-02-06 10:33:32 +01:00
04a1412562 upgrade ckeditor/vue 2025-02-06 10:33:32 +01:00
3aef0a185e Implement localStorage and toggling of simple/rich text editor 2025-02-06 10:33:32 +01:00
578bce31b9 Setup new component for text editor 2025-02-06 10:33:32 +01:00
99e4824137 Release bundles v.3.8.1 2025-02-05 18:14:22 +01:00
dacaaea235 Fix household link in parcours banner 2025-02-05 18:09:34 +01:00
096466e79e Add missing lines for v3.8.0 2025-02-03 22:22:11 +01:00
7285e5c2b0 Merge branch '331-manage-attachments-to-workflow' into 'master'
Add attachments to workflow

Closes #331

See merge request Chill-Projet/chill-bundles!764
2025-02-03 21:15:00 +00:00
37227a3aeb Add attachments to workflow 2025-02-03 21:15:00 +00:00
7569667189 Release v3.8.0 2025-02-03 22:11:42 +01:00
b0993f4062 Merge branch '334-current-user-activity-suggestion' into 'master'
Resolve "Quand on crée un échange, l'utilisateur courant doit toujours être suggéré"

Closes #334

See merge request Chill-Projet/chill-bundles!786
2025-02-03 21:08:49 +00:00
7c79b65f48 Merge branch '350-validation_person_duplicate' into 'master'
Resolve "Erreur 500 lorsqu'un utilisateur fusionne deux dossiers d'usagers, et que les deux dossiers usagers sélectionnés sont les mêmes"

Closes #350

See merge request Chill-Projet/chill-bundles!788
2025-02-03 21:04:28 +00:00
b8f25bcd45 Resolve "Erreur 500 lorsqu'un utilisateur fusionne deux dossiers d'usagers, et que les deux dossiers usagers sélectionnés sont les mêmes" 2025-02-03 21:04:27 +00:00
f4efb0e975 Merge branch '351-prettier_span_fix' into 'master'
Resolve "UX bug caused by 'Prettier' linter"

Closes #351

See merge request Chill-Projet/chill-bundles!790
2025-02-03 21:02:46 +00:00
c641baec78 Resolve "UX bug caused by 'Prettier' linter" 2025-02-03 21:02:44 +00:00
cc150e32f0 Merge branch '354-fix_creation_document_category' into 'master'
Resolve "chill_doc: cannot create a document category"

Closes #354

See merge request Chill-Projet/chill-bundles!791
2025-02-03 21:02:03 +00:00
Christophe Siraut
bc7f0907ab doc: add languages population command 2025-02-03 14:05:08 +01:00
26cf6459b4 Add changie 2025-02-03 11:23:12 +01:00
d0fa6dd512 Apply fix to query idInsideBundle for documentCategory creation 2025-02-03 11:21:00 +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
03748a7e84 Add changie 2025-01-30 12:00:11 +01:00
9e3431f397 Merge branch 'improve_news_item_admin_form' into 'master'
Improve ux news item admin form

See merge request Chill-Projet/chill-bundles!779
2025-01-27 10:59:51 +00:00
912861dbff Remove assert attribute on startdate 2025-01-27 11:03:46 +01:00
35f25daf7c Merge branch '320-display-persons-in-banner' into 'master'
Display first associated persons in the banner of an accompanying course

Closes #320

See merge request Chill-Projet/chill-bundles!769
2025-01-23 20:19:31 +00:00
21274155b5 Display first associated persons in the banner of an accompanying course 2025-01-23 20:19:31 +00:00
3f7c136d6b Remove yarn.lock from tracking 2025-01-23 12:40:39 +01:00
5d9c573853 Merge branch '319-display-info-in-notification' into 'master'
Resolve "Afficher les noms des usagers et l'entité concerné par l'entité notifiée dans la liste des notifications"

Closes #319

See merge request Chill-Projet/chill-bundles!767
2025-01-23 11:34:16 +00:00
9a5fd67842 Resolve "Afficher les noms des usagers et l'entité concerné par l'entité notifiée dans la liste des notifications" 2025-01-23 11:34:16 +00:00
2755bc12c4 Add current user to suggested users when creating activity 2025-01-22 15:41:25 +01:00
9e191f1b5b Restrict eslint on src directory 2025-01-22 14:19:30 +00:00
59fd9fc63f Add a line in the documentation about the role of prettier within eslint configuration 2025-01-21 10:36:45 +01:00
eea1e40663 Merge branch 'improve_news_item_admin_form' of https://gitlab.com/Chill-Projet/chill-bundles into improve_news_item_admin_form 2025-01-13 16:20:08 +01:00
1b0771eb07 Add changie 2025-01-13 16:19:52 +01:00
3a74c48104 Indicate user to set startDate equal to or greater than today + set empty_data in form content field 2025-01-13 16:19:52 +01:00
b37d7fb907 Add changie 2025-01-08 16:21:36 +01:00
57b8dacba0 Indicate user to set startDate equal to or greater than today + set empty_data in form content field 2025-01-08 16:18:13 +01:00
324 changed files with 8150 additions and 10747 deletions

View File

@@ -0,0 +1,6 @@
kind: DX
body: Remove dead code for wopi-link module
time: 2025-04-30T14:45:50.406111606+02:00
custom:
Issue: "352"
SchemaChange: No schema change

View File

@@ -0,0 +1,7 @@
kind: Feature
body: Add the document file name to the document title when a user upload a document,
unless there is already a document title.
time: 2025-04-24T14:22:11.800975422+02:00
custom:
Issue: "377"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Add desactivation date for social action and issue csv export
time: 2025-05-20T09:56:28.108941934+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -0,0 +1,7 @@
kind: Fixed
body: trying to prevent bug of typeerror in doc-history + improved display of document
history
time: 2025-04-24T13:39:43.878468232+02:00
custom:
Issue: "376"
SchemaChange: No schema change

View File

@@ -0,0 +1,7 @@
kind: Fixed
body: Display previous participation in acc course work even if the person has left
the acc course
time: 2025-04-24T16:37:46.970203594+02:00
custom:
Issue: "381"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: Fix display of text in calendar events
time: 2025-05-05T10:27:15.461493066+02:00
custom:
Issue: "372"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: Add missing translation for user_group.no_user_groups
time: 2025-05-14T14:53:39.53927329+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: UX
body: Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page
time: 2025-04-23T17:26:24.45777387+02:00
custom:
Issue: "374"
SchemaChange: No schema change

6
.changes/v3.10.0.md Normal file
View File

@@ -0,0 +1,6 @@
## v3.10.0 - 2025-03-17
### Feature
* ([#363](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/363)) Display social actions grouped per social issue within activity form
### Fixed
* ([#362](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/362)) Fix Dependency Injection, which prevented to save the CalendarRange
* ([#368](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/368)) fix search query for user groups

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

@@ -0,0 +1,3 @@
## v3.10.1 - 2025-03-17
### DX
* Remove yarn dependency to symfony/ux-translator, to ease the build process

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

@@ -0,0 +1,3 @@
## v3.10.2 - 2025-03-17
### Fixed
* Replace a ts-expect-error with a ts-ignore

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

@@ -0,0 +1,3 @@
## v3.10.3 - 2025-03-18
### DX
* Eslint fixes

19
.changes/v3.11.0.md Normal file
View File

@@ -0,0 +1,19 @@
## v3.11.0 - 2025-04-17
### Feature
* ([#365](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/365)) Add counters of actions and activities, with 2 boxes to (1) show the number of active actions on total actions and (2) show the number of activities in a accompanying period, and pills in menus for showing the number of active actions and the number of activities.
* ([#364](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/364)) Added a second phone number "telephone2" to the thirdParty entity. Adapted twig templates and vuejs apps to handle this phone number
**Schema Change**: Add columns or tables
* Signature: add a button to go directly to the signature zone, even if there is only one
### Fixed
* Fixed wrong translations in the on-the-fly for creation of thirdParty
* Fixed update of phone number in on-the-fly edition of thirdParty
* Fixed closing of modal when editing thirdParty in accompanying course works
* Shorten the delay between two execution of AccompanyingPeriodStepChangeCronjob, to ensure at least one execution in a day
* ([#102](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/102)) Fix display of title in document list
* When cleaning the old stored object versions, do not throw an error if the stored object is not found on disk
* Add consistent log prefix and key to logs when stale workflows are automatically canceled
* ([#380](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/380)) Remove the "not null" validation constraint on recently added properties on HouseholdComposition
### DX
* Add new chill-col style for displaying title and aside in a flex table

11
.changes/v3.8.0.md Normal file
View File

@@ -0,0 +1,11 @@
## v3.8.0 - 2025-02-03
### Feature
* Improve the UX of the news item admin form to prevent wrong usage
* ([#319](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/319)) Notification list: display the concerned person's badges in the list
* ([#320](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/320)) Show the first 3 persons directly in the accompanying period's banner
* ([#334](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/334)) Suggest current user when creating an activity
* ([#331](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/331)) Add attachments to workflows
### Fixed
* ([#350](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/350)) Add validation error to manual selection of person in PersonDuplicateController
* ([#354](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/354)) Fix document category creation
* ([#351](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/351)) Add definitive whitespace between span elements in vue PersonText component

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

@@ -0,0 +1,3 @@
## v3.8.1 - 2025-02-05
### Fixed
* Fix household link in the parcours banner

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

10
.changes/v3.9.0.md Normal file
View File

@@ -0,0 +1,10 @@
## v3.9.0 - 2025-02-27
### Feature
* ([#349](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/349)) Suggest all referrers within actions of the accompanying period when creating an activity
* ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add possibility to export a csv with all social issues and social actions
* ([#360](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/360)) Restore document to previous kept version when a workflow is canceled
* ([#341](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/341)) Add a list of third parties from within the admin (csv download)
### Fixed
* fix generation of document with accompanying period context, and list of activities and works
### DX
* ([#333](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/333)) Create an unique source of trust for translations

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

@@ -0,0 +1,3 @@
## v3.9.1 - 2025-02-27
### Fixed
* Fix post/patch request with missing 'type' property for gender

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

@@ -0,0 +1,3 @@
## v3.9.2 - 2025-02-27
### Fixed
* Use fetchResults method to fetch all social issues instead of only the first page

3
.gitignore vendored
View File

@@ -5,12 +5,15 @@ composer.lock
docs/build/
.php_cs.cache
.cache/*
yarn.lock
docker/db/data
docker/rabbitmq/data
# in this development bundle, we want to ignore directories related to a real app
assets/*
!assets/translator.ts
!assets/ux-translator
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,64 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
## v3.10.3 - 2025-03-18
### DX
* Eslint fixes
## v3.10.2 - 2025-03-17
### Fixed
* Replace a ts-expect-error with a ts-ignore
## v3.10.1 - 2025-03-17
### DX
* Remove yarn dependency to symfony/ux-translator, to ease the build process
## v3.10.0 - 2025-03-17
### Feature
* ([#363](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/363)) Display social actions grouped per social issue within activity form
### Fixed
* ([#362](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/362)) Fix Dependency Injection, which prevented to save the CalendarRange
* ([#368](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/368)) fix search query for user groups
## v3.9.2 - 2025-02-27
### Fixed
* Use fetchResults method to fetch all social issues instead of only the first page
## v3.9.1 - 2025-02-27
### Fixed
* Fix post/patch request with missing 'type' property for gender
## v3.9.0 - 2025-02-27
### Feature
* ([#349](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/349)) Suggest all referrers within actions of the accompanying period when creating an activity
* ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add possibility to export a csv with all social issues and social actions
* ([#360](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/360)) Restore document to previous kept version when a workflow is canceled
* ([#341](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/341)) Add a list of third parties from within the admin (csv download)
### Fixed
* fix generation of document with accompanying period context, and list of activities and works
### DX
* ([#333](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/333)) Create an unique source of trust for translations
## 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
## v3.8.0 - 2025-02-03
### Feature
* Improve the UX of the news item admin form to prevent wrong usage
* ([#319](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/319)) Notification list: display the concerned person's badges in the list
* ([#320](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/320)) Show the first 3 persons directly in the accompanying period's banner
* ([#334](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/334)) Suggest current user when creating an activity
* ([#331](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/331)) Add attachments to workflows
### Fixed
* ([#350](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/350)) Add validation error to manual selection of person in PersonDuplicateController
* ([#354](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/354)) Fix document category creation
* ([#351](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/351)) Add definitive whitespace between span elements in vue PersonText component
## v3.7.1 - 2025-01-21
### Fixed
* Fix legacy configuration processor for notifier component

7
assets/translator.ts Normal file
View File

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

View File

@@ -0,0 +1,3 @@
This directory import the symfony ux-translator files directly into chill-bundles.
This remove the yarn dependencies from the real package, which breaks our installation.

View File

@@ -0,0 +1 @@
export declare function format(id: string, parameters: Record<string, string | number>, locale: string): string;

View File

@@ -0,0 +1 @@
export declare function formatIntl(id: string, parameters: Record<string, string | number>, locale: string): string;

View File

@@ -0,0 +1,27 @@
export type DomainType = string;
export type LocaleType = string;
export type TranslationsType = Record<DomainType, {
parameters: ParametersType;
}>;
export type NoParametersType = Record<string, never>;
export type ParametersType = Record<string, string | number | Date> | NoParametersType;
export type RemoveIntlIcuSuffix<T> = T extends `${infer U}+intl-icu` ? U : T;
export type DomainsOf<M> = M extends Message<infer Translations, LocaleType> ? keyof Translations : never;
export type LocaleOf<M> = M extends Message<TranslationsType, infer Locale> ? Locale : never;
export type ParametersOf<M, D extends DomainType> = M extends Message<infer Translations, LocaleType> ? Translations[D] extends {
parameters: infer Parameters;
} ? Parameters : never : never;
export interface Message<Translations extends TranslationsType, Locale extends LocaleType> {
id: string;
translations: {
[domain in DomainType]: {
[locale in Locale]: string;
};
};
}
export declare function setLocale(locale: LocaleType | null): void;
export declare function getLocale(): LocaleType;
export declare function throwWhenNotFound(enabled: boolean): void;
export declare function setLocaleFallbacks(localeFallbacks: Record<LocaleType, LocaleType>): void;
export declare function getLocaleFallbacks(): Record<LocaleType, LocaleType>;
export declare function trans<M extends Message<TranslationsType, LocaleType>, D extends DomainsOf<M>, P extends ParametersOf<M, D>>(...args: P extends NoParametersType ? [message: M, parameters?: P, domain?: RemoveIntlIcuSuffix<D>, locale?: LocaleOf<M>] : [message: M, parameters: P, domain?: RemoveIntlIcuSuffix<D>, locale?: LocaleOf<M>]): string;

View File

@@ -0,0 +1 @@
export * from './translator';

View File

@@ -0,0 +1,283 @@
import { IntlMessageFormat } from 'intl-messageformat';
function strtr(string, replacePairs) {
const regex = Object.entries(replacePairs).map(([from]) => {
return from.replace(/([-[\]{}()*+?.\\^$|#,])/g, '\\$1');
});
if (regex.length === 0) {
return string;
}
return string.replace(new RegExp(regex.join('|'), 'g'), (matched) => replacePairs[matched].toString());
}
function format(id, parameters, locale) {
if (null === id || '' === id) {
return '';
}
if (typeof parameters['%count%'] === 'undefined' || Number.isNaN(parameters['%count%'])) {
return strtr(id, parameters);
}
const number = Number(parameters['%count%']);
let parts = [];
if (/^\|+$/.test(id)) {
parts = id.split('|');
}
else {
parts = id.match(/(?:\|\||[^|])+/g) || [];
}
const intervalRegex = /^(?<interval>({\s*(-?\d+(\.\d+)?[\s*,\s*\-?\d+(.\d+)?]*)\s*})|(?<left_delimiter>[[\]])\s*(?<left>-Inf|-?\d+(\.\d+)?)\s*,\s*(?<right>\+?Inf|-?\d+(\.\d+)?)\s*(?<right_delimiter>[[\]]))\s*(?<message>.*?)$/s;
const standardRules = [];
for (let part of parts) {
part = part.trim().replace(/\|\|/g, '|');
const matches = part.match(intervalRegex);
if (matches) {
const matchGroups = matches.groups || {};
if (matches[2]) {
for (const n of matches[3].split(',')) {
if (number === Number(n)) {
return strtr(matchGroups.message, parameters);
}
}
}
else {
const leftNumber = '-Inf' === matchGroups.left ? Number.NEGATIVE_INFINITY : Number(matchGroups.left);
const rightNumber = ['Inf', '+Inf'].includes(matchGroups.right)
? Number.POSITIVE_INFINITY
: Number(matchGroups.right);
if (('[' === matchGroups.left_delimiter ? number >= leftNumber : number > leftNumber) &&
(']' === matchGroups.right_delimiter ? number <= rightNumber : number < rightNumber)) {
return strtr(matchGroups.message, parameters);
}
}
}
else {
const ruleMatch = part.match(/^\w+:\s*(.*?)$/);
standardRules.push(ruleMatch ? ruleMatch[1] : part);
}
}
const position = getPluralizationRule(number, locale);
if (typeof standardRules[position] === 'undefined') {
if (1 === parts.length && typeof standardRules[0] !== 'undefined') {
return strtr(standardRules[0], parameters);
}
throw new Error(`Unable to choose a translation for "${id}" with locale "${locale}" for value "${number}". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %count% apples").`);
}
return strtr(standardRules[position], parameters);
}
function getPluralizationRule(number, locale) {
number = Math.abs(number);
let _locale = locale;
if (locale === 'pt_BR' || locale === 'en_US_POSIX') {
return 0;
}
_locale = _locale.length > 3 ? _locale.substring(0, _locale.indexOf('_')) : _locale;
switch (_locale) {
case 'af':
case 'bn':
case 'bg':
case 'ca':
case 'da':
case 'de':
case 'el':
case 'en':
case 'en_US_POSIX':
case 'eo':
case 'es':
case 'et':
case 'eu':
case 'fa':
case 'fi':
case 'fo':
case 'fur':
case 'fy':
case 'gl':
case 'gu':
case 'ha':
case 'he':
case 'hu':
case 'is':
case 'it':
case 'ku':
case 'lb':
case 'ml':
case 'mn':
case 'mr':
case 'nah':
case 'nb':
case 'ne':
case 'nl':
case 'nn':
case 'no':
case 'oc':
case 'om':
case 'or':
case 'pa':
case 'pap':
case 'ps':
case 'pt':
case 'so':
case 'sq':
case 'sv':
case 'sw':
case 'ta':
case 'te':
case 'tk':
case 'ur':
case 'zu':
return 1 === number ? 0 : 1;
case 'am':
case 'bh':
case 'fil':
case 'fr':
case 'gun':
case 'hi':
case 'hy':
case 'ln':
case 'mg':
case 'nso':
case 'pt_BR':
case 'ti':
case 'wa':
return number < 2 ? 0 : 1;
case 'be':
case 'bs':
case 'hr':
case 'ru':
case 'sh':
case 'sr':
case 'uk':
return 1 === number % 10 && 11 !== number % 100
? 0
: number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)
? 1
: 2;
case 'cs':
case 'sk':
return 1 === number ? 0 : number >= 2 && number <= 4 ? 1 : 2;
case 'ga':
return 1 === number ? 0 : 2 === number ? 1 : 2;
case 'lt':
return 1 === number % 10 && 11 !== number % 100
? 0
: number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20)
? 1
: 2;
case 'sl':
return 1 === number % 100 ? 0 : 2 === number % 100 ? 1 : 3 === number % 100 || 4 === number % 100 ? 2 : 3;
case 'mk':
return 1 === number % 10 ? 0 : 1;
case 'mt':
return 1 === number
? 0
: 0 === number || (number % 100 > 1 && number % 100 < 11)
? 1
: number % 100 > 10 && number % 100 < 20
? 2
: 3;
case 'lv':
return 0 === number ? 0 : 1 === number % 10 && 11 !== number % 100 ? 1 : 2;
case 'pl':
return 1 === number
? 0
: number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 12 || number % 100 > 14)
? 1
: 2;
case 'cy':
return 1 === number ? 0 : 2 === number ? 1 : 8 === number || 11 === number ? 2 : 3;
case 'ro':
return 1 === number ? 0 : 0 === number || (number % 100 > 0 && number % 100 < 20) ? 1 : 2;
case 'ar':
return 0 === number
? 0
: 1 === number
? 1
: 2 === number
? 2
: number % 100 >= 3 && number % 100 <= 10
? 3
: number % 100 >= 11 && number % 100 <= 99
? 4
: 5;
default:
return 0;
}
}
function formatIntl(id, parameters, locale) {
if (id === '') {
return '';
}
const intlMessage = new IntlMessageFormat(id, [locale.replace('_', '-')], undefined, { ignoreTag: true });
parameters = { ...parameters };
Object.entries(parameters).forEach(([key, value]) => {
if (key.includes('%') || key.includes('{')) {
delete parameters[key];
parameters[key.replace(/[%{} ]/g, '').trim()] = value;
}
});
return intlMessage.format(parameters);
}
let _locale = null;
let _localeFallbacks = {};
let _throwWhenNotFound = false;
function setLocale(locale) {
_locale = locale;
}
function getLocale() {
return (_locale ||
document.documentElement.getAttribute('data-symfony-ux-translator-locale') ||
(document.documentElement.lang ? document.documentElement.lang.replace('-', '_') : null) ||
'en');
}
function throwWhenNotFound(enabled) {
_throwWhenNotFound = enabled;
}
function setLocaleFallbacks(localeFallbacks) {
_localeFallbacks = localeFallbacks;
}
function getLocaleFallbacks() {
return _localeFallbacks;
}
function trans(message, parameters = {}, domain = 'messages', locale = null) {
if (typeof domain === 'undefined') {
domain = 'messages';
}
if (typeof locale === 'undefined' || null === locale) {
locale = getLocale();
}
if (typeof message.translations === 'undefined') {
return message.id;
}
const localesFallbacks = getLocaleFallbacks();
const translationsIntl = message.translations[`${domain}+intl-icu`];
if (typeof translationsIntl !== 'undefined') {
while (typeof translationsIntl[locale] === 'undefined') {
locale = localesFallbacks[locale];
if (!locale) {
break;
}
}
if (locale) {
return formatIntl(translationsIntl[locale], parameters, locale);
}
}
const translations = message.translations[domain];
if (typeof translations !== 'undefined') {
while (typeof translations[locale] === 'undefined') {
locale = localesFallbacks[locale];
if (!locale) {
break;
}
}
if (locale) {
return format(translations[locale], parameters, locale);
}
}
if (_throwWhenNotFound) {
throw new Error(`No translation message found with id "${message.id}".`);
}
return message.id;
}
export { getLocale, getLocaleFallbacks, setLocale, setLocaleFallbacks, throwWhenNotFound, trans };

1
assets/ux-translator/dist/utils.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export declare function strtr(string: string, replacePairs: Record<string, string | number>): string;

View File

@@ -0,0 +1,34 @@
{
"name": "@symfony/ux-translator",
"description": "Symfony Translator for JavaScript",
"license": "MIT",
"version": "1.0.0",
"main": "dist/translator_controller.js",
"types": "dist/translator_controller.d.ts",
"scripts": {
"build": "node ../../../bin/build_package.js .",
"watch": "node ../../../bin/build_package.js . --watch",
"test": "../../../bin/test_package.sh .",
"check": "biome check",
"ci": "biome ci"
},
"symfony": {
"importmap": {
"intl-messageformat": "^10.5.11",
"@symfony/ux-translator": "path:%PACKAGE%/dist/translator_controller.js",
"@app/translations": "path:var/translations/index.js",
"@app/translations/configuration": "path:var/translations/configuration.js"
}
},
"peerDependencies": {
"intl-messageformat": "^10.5.11"
},
"peerDependenciesMeta": {
"intl-messageformat": {
"optional": false
}
},
"devDependencies": {
"intl-messageformat": "^10.5.11"
}
}

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

@@ -220,6 +220,7 @@ framework:
- attenteModification
- attenteMiseEnForme
- attenteValidationMiseEnForme
- attenteSignature
- attenteVisa
- postSignature
- attenteTraitement

View File

@@ -0,0 +1,19 @@
when@dev:
sass_assets:
path: /_dev/assets
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: '@ChillMain/Dev/dev.assets.html.twig'
sass_assets_test1:
path: /_dev/assets_test1
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: '@ChillMain/Dev/dev.assets.test1.html.twig'
sass_assets_test2:
path: /_dev/assets_test2
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: '@ChillMain/Dev/dev.assets.test2.html.twig'

View File

@@ -0,0 +1,12 @@
when@dev:
swagger_ui:
path: /_dev/swagger
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: '@ChillMain/Dev/swagger-ui/index.html.twig'
swagger_specs:
path: /_dev/specs.yaml
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: api/specs.yaml

View File

@@ -12,6 +12,8 @@ This runs eslint **not** taking the baseline into account, thus showing all exis
A script was also added to package.json allowing you to execute ``yarn run eslint``.
This will run eslint, but **taking the baseline into account**, thus only alerting to newly created errors.
The eslint command is configured to also run ``prettier`` which will simply format the code to look more uniform (takes care indentation for example).
Interesting options that can be used in combination with eslint are:
- ``--quiet`` to only get errors and silence the warnings

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

@@ -34,6 +34,7 @@ export default ts.config(
// override/add rules settings here, such as:
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/ban-ts-comment": "off"
},
},
);

View File

@@ -6,31 +6,29 @@
"@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/node14": "^1.0.1",
"@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5",
"@types/eslint__js": "^8.42.3",
"@typescript-eslint/parser": "^8.12.2",
"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",
"marked": "^12.0.1",
"node-sass": "^8.0.0",
"popper.js": "^1.16.1",
"postcss-loader": "^7.0.2",
@@ -55,11 +53,13 @@
"@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",
"dropzone": "^5.7.6",
"es6-promise": "^4.2.8",
"intl-messageformat": "^10.5.11",
"leaflet": "^1.7.1",
"marked": "^12.0.2",
"masonry-layout": "^4.2.2",
@@ -73,14 +73,19 @@
"vuex": "^4.0.0"
},
"browserslist": [
"Firefox ESR"
"defaults and fully supports es6-module and not dead"
],
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress",
"eslint": "npx eslint-baseline --fix \"**/*.{js,ts,vue}\""
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
"specs-create-dir": "mkdir -p templates/api",
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
"version": "node --version",
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
},
"private": true
}

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
@@ -23,22 +24,30 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{
public function __construct(protected Security $security, protected TranslatorInterface $translator) {}
public function __construct(
protected Security $security,
protected TranslatorInterface $translator,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
) {}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$period = $parameters['accompanyingCourse'];
$activities = $this->managerRegistry->getManager()->getRepository(Activity::class)->findBy(
['accompanyingPeriod' => $period]
);
if (
AccompanyingPeriod::STEP_DRAFT !== $period->getStep()
&& $this->security->isGranted(ActivityVoter::SEE, $period)
) {
$menu->addChild($this->translator->trans('Activity'), [
$menu->addChild($this->translator->trans('Activities'), [
'route' => 'chill_activity_activity_list',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 40]);
->setExtras(['order' => 40, 'counter' => count($activities) > 0 ? count($activities) : null]);
}
}

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\Person;
@@ -23,13 +24,20 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
{
public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator) {}
public function __construct(
private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
private AuthorizationCheckerInterface $authorizationChecker,
private TranslatorInterface $translator,
) {}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
/** @var Person $person */
$person = $parameters['person'];
$count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE);
if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) {
$menu->addChild(
$this->translator->trans('Activities'),
@@ -38,7 +46,7 @@ final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
'routeParameters' => ['person_id' => $person->getId()],
]
)
->setExtra('order', 201);
->setExtras(['order' => 201, 'counter' => $count > 0 ? $count : null]);
}
}

View File

@@ -15,10 +15,13 @@ use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Contracts\Translation\TranslatableInterface;
final readonly class ActivityNotificationHandler implements NotificationHandlerInterface
{
public function __construct(private ActivityRepository $activityRepository) {}
public function __construct(private ActivityRepository $activityRepository, private TranslatableStringHelperInterface $translatableStringHelper) {}
public function getTemplate(Notification $notification, array $options = []): string
{
@@ -37,4 +40,30 @@ final readonly class ActivityNotificationHandler implements NotificationHandlerI
{
return Activity::class === $notification->getRelatedEntityClass();
}
public function getTitle(Notification $notification, array $options = []): TranslatableInterface
{
if (null === $activity = $this->getRelatedEntity($notification)) {
return new TranslatableMessage('activity.deleted');
}
return new TranslatableMessage('activity.title', [
'date' => $activity->getDate(),
'type' => $this->translatableStringHelper->localize($activity->getActivityType()->getName()),
]);
}
public function getAssociatedPersons(Notification $notification, array $options = []): array
{
if (null === $activity = $this->getRelatedEntity($notification)) {
return [];
}
return $activity->getPersonsAssociated();
}
public function getRelatedEntity(Notification $notification): ?Activity
{
return $this->activityRepository->find($notification->getRelatedEntityId());
}
}

View File

@@ -120,3 +120,34 @@ li.document-list-item {
vertical-align: baseline;
}
}
.badge-activity-type-simple {
@extend .badge;
display: inline-block;
margin: 0.2rem 0;
padding-left: 0;
padding-right: 0.5rem;
border-left: 20px groove #9acd32;
border-radius: $badge-border-radius;
color: black;
font-weight: normal;
font-size: unset;
max-width: 100%;
background-color: $gray-100;
overflow: hidden;
text-overflow: ellipsis;
text-indent: 5px hanging;
text-align: left;
&::before {
margin-right: 3px;
position: relative;
left: -0.5px;
font-family: ForkAwesome;
content: '\f04b';
color: #9acd32;
}
}

View File

@@ -11,7 +11,7 @@ import Location from "./components/Location.vue";
export default {
name: "App",
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
props: ["hasSocialIssues", "hasLocation", "hasPerson", "isSimpleEditor"],
components: {
ConcernedGroups,
SocialIssuesAcc,

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,35 +44,46 @@
<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)
"
>
<div
id="actionsList"
v-for="group in socialActionsList"
:key="group.issue"
>
<span class="badge bg-chill-l-gray text-dark">{{
group.issue
}}</span>
<check-social-action
v-for="action in socialActionsList"
v-for="action in group.actions"
:key="action.id"
:action="action"
:selection="socialActionsSelected"
@update-selected="updateActionsSelected"
/>
@updateSelected="updateActionsSelected"
>
</check-social-action>
</div>
</template>
@@ -80,7 +93,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 +105,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 +121,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,
@@ -127,53 +158,44 @@ export default {
},
},
mounted() {
/* Load others issues in multiselect
*/
/* Load other issues in multiselect */
this.issueIsLoading = true;
this.actionAreLoaded = false;
getSocialIssues().then(
(response) =>
new Promise((resolve, reject) => {
this.$store.commit("updateIssuesOther", response.results);
/* Add in list the issues already associated (if not yet listed)
*/
getSocialIssues().then((response) => {
/* Add issues to the store */
this.$store.commit("updateIssuesOther", response);
/* Add in list the issues already associated (if not yet listed) */
this.socialIssuesSelected.forEach((issue) => {
if (
this.socialIssuesList.filter(
(i) => i.id === issue.id,
).length !== 1
this.socialIssuesList.filter((i) => i.id === issue.id)
.length !== 1
) {
this.$store.commit("addIssueInList", issue);
}
}, this);
});
/* Remove from multiselect the issues that are not yet in checkbox list
*/
/* Remove from multiselect the issues that are not yet in the checkbox list */
this.socialIssuesList.forEach((issue) => {
this.$store.commit("removeIssueInOther", issue);
}, this);
});
/* Filter issues
*/
/* Filter issues */
this.$store.commit("filterList", "issues");
/* Add in list the actions already associated (if not yet listed)
*/
/* Add in list the actions already associated (if not yet listed) */
this.socialActionsSelected.forEach((action) => {
this.$store.commit("addActionInList", action);
}, this);
});
/* Filter issues
*/
/* Filter actions */
this.$store.commit("filterList", "actions");
this.issueIsLoading = false;
this.actionAreLoaded = true;
this.updateActionsList();
resolve();
}),
);
});
},
methods: {
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
@@ -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,9 +257,24 @@ export default {
};
</script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
<style lang="scss" scoped>
@import "ChillMainAssets/module/bootstrap/shared";
@import "ChillPersonAssets/chill/scss/mixins";
@import "ChillMainAssets/chill/scss/chill_variables";
span.multiselect__single {
display: none !important;
}
#actionsList {
border-radius: 0.5rem;
padding: 1rem;
margin: 0.5rem;
background-color: whitesmoke;
}
span.badge {
margin-bottom: 0.5rem;
@include badge_social($social-issue-color);
}
</style>

View File

@@ -10,7 +10,9 @@
:value="action"
/>
<label class="form-check-label" :for="action.id">
<span class="badge bg-light text-dark">{{ action.text }}</span>
<span class="badge bg-light text-dark" :title="action.text">{{
action.text
}}</span>
</label>
</div>
</span>
@@ -43,5 +45,9 @@ span.badge {
font-size: 95%;
margin-bottom: 5px;
margin-right: 1em;
max-width: 100%; /* Adjust as needed */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -14,18 +14,21 @@ const i18n = _createI18n(activityMessages);
const hasSocialIssues = document.querySelector("#social-issues-acc") !== null;
const hasLocation = document.querySelector("#location") !== null;
const hasPerson = document.querySelector("#add-persons") !== null;
const isSimpleEditor = true;
const app = createApp({
template: `<app
:hasSocialIssues="hasSocialIssues"
:hasLocation="hasLocation"
:hasPerson="hasPerson"
:isSimpleEditor = "isSimpleEditor"
></app>`,
data() {
return {
hasSocialIssues,
hasLocation,
hasPerson,
isSimpleEditor
};
},
})

View File

@@ -2,6 +2,7 @@ import "es6-promise/auto";
import { createStore } from "vuex";
import { postLocation } from "./api";
import prepareLocations from "./store.locations.js";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
const debug = process.env.NODE_ENV !== "production";
//console.log('window.activity', window.activity);
@@ -23,7 +24,9 @@ const removeIdFromValue = (string, id) => {
const store = createStore({
strict: debug,
state: {
me: null,
activity: window.activity,
accompanyingPeriodWorks: [],
socialIssuesOther: [],
socialActionsList: [],
availableLocations: [],
@@ -39,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 = [
@@ -78,16 +81,32 @@ const store = createStore({
state.activity.activityType.thirdPartiesVisible !== 0),
);
},
suggestedUser(state) {
suggestedUsers(state) {
const existingUserIds = state.activity.users.map((p) => p.id);
return state.activity.activityType.usersVisible === 0
let suggestedUsers =
state.activity.activityType.usersVisible === 0
? []
: [state.activity.accompanyingPeriod.user].filter(
(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);
return suggestedUsers;
},
suggestedResources(state) {
const resources = state.activity.accompanyingPeriod.resources;
// const resources = state.activity.accompanyingPeriod.resources;
const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(
(p) => p.id,
@@ -105,12 +124,25 @@ const store = createStore({
);
},
socialActionsListSorted(state) {
return [...state.socialActionsList].sort(
(a, b) => a.ordering - b.ordering,
);
return [...state.socialActionsList]
.sort((a, b) => a.ordering - b.ordering)
.reduce((acc, action) => {
const issueText = action.issue?.text || "Uncategorized";
// Find if the group for the issue already exists
let group = acc.find((item) => item.issue === issueText);
if (!group) {
group = { issue: issueText, actions: [] };
acc.push(group);
}
group.actions.push(action);
return acc;
}, []);
},
},
mutations: {
setWhoAmI(state, me) {
state.me = me;
},
// SocialIssueAcc
addIssueInList(state, issue) {
//console.log('add issue list', issue.id);
@@ -208,6 +240,9 @@ const store = createStore({
addAvailableLocationGroup(state, group) {
state.availableLocations.push(group);
},
setAccompanyingPeriodWorks(state, works) {
state.accompanyingPeriodWorks = works;
},
},
actions: {
addIssueSelected({ commit }, issue) {
@@ -326,9 +361,29 @@ 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) => {
commit("setWhoAmI", user);
});
},
},
});
store.dispatch("getWhoAmI");
prepareLocations(store);
store.dispatch("fetchAccompanyingPeriodWorks");
export default store;

View File

@@ -1,83 +1,3 @@
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
{% set person_id = null %}
{% if activity.person %}
{% set person_id = activity.person.id %}
{% endif %}
{% set accompanying_course_id = null %}
{% if activity.accompanyingPeriod %}
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
{% endif %}
<div class="item-bloc activity-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<div class="item-row">
<div class="item-col" style="width: unset">
{% if document.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.isFailure %}
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
{% endif %}
<div>
{% if activity.accompanyingPeriod is not null and context == 'person' %}
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
</span>&nbsp;
{% endif %}
<div class="badge-activity-type">
<span class="title_label"></span>
<span class="title_action">
{{ activity.type.name | localize_translatable_string }}
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</span>
</div>
</div>
<div class="denomination h2">
{{ document.title|chill_print_or_message("No title") }}
</div>
{% if document.hasTemplate %}
<div>
<p>{{ document.template.name|localize_translatable_string }}</p>
</div>
{% endif %}
</div>
<div class="item-col">
<div class="container">
<div class="dates row text-end">
<span>{{ document.createdAt|format_date('short') }}</span>
</div>
</div>
</div>
</div>
<div class="item-row separator">
<div class="item-col item-meta">
{{ mmm.createdBy(document) }}
</div>
<ul class="item-col record_actions flex-shrink-1">
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
<li>
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
</li>
{% endif %}
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
<li>
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
<li>
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
</li>
{% endif %}
</ul>
</div>
{{ include('@ChillActivity/GenericDoc/activity_document_row.html.twig') }}
</div>

View File

@@ -0,0 +1,81 @@
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
{% set person_id = null %}
{% if activity.person %}
{% set person_id = activity.person.id %}
{% endif %}
{% set accompanying_course_id = null %}
{% if activity.accompanyingPeriod %}
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
{% endif %}
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
{% if document.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.isFailure %}
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
{% endif %}
<div>
<div>
<div class="badge-activity-type-simple">
{{ activity.type.name | localize_translatable_string }}
</div>
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</div>
</div>
<div class="denomination h2">
{{ document.title|chill_print_or_message("No title") }}
</div>
{% if document.hasTemplate %}
<div>
<p>{{ document.template.name|localize_translatable_string }}</p>
</div>
{% endif %}
</div>
<div class="aside">
<div class="dates row text-end">
<span>{{ document.createdAt|format_date('short') }}</span>
</div>
{% if activity.accompanyingPeriod is not null and context == 'person' %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
</span>&nbsp;
</div>
{% endif %}
</div>
</div>
</div>
{% if show_actions %}
<div class="item-row separator">
<div class="item-col item-meta">
{{ mmm.createdBy(document) }}
</div>
<ul class="item-col record_actions flex-shrink-1">
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
<li>
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
</li>
{% endif %}
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
<li>
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
<li>
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
</li>
{% endif %}
</ul>
</div>
{% endif %}

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

@@ -0,0 +1,54 @@
<?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\ActivityBundle\Service\GenericDoc\Normalizer;
use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider;
use Chill\ActivityBundle\Service\GenericDoc\Renderers\AccompanyingPeriodActivityGenericDocRenderer;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
final readonly class AccompanyingPeriodActivityGenericDocNormalizer implements GenericDocNormalizerInterface
{
public function __construct(
private StoredObjectRepositoryInterface $storedObjectRepository,
private AccompanyingPeriodActivityGenericDocRenderer $renderer,
private Environment $twig,
private TranslatorInterface $translator,
) {}
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
{
return AccompanyingPeriodActivityGenericDocProvider::KEY === $genericDocDTO->key
&& 'json' == $format;
}
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
{
$storedObject = $this->storedObjectRepository->find($genericDocDTO->identifiers['id']);
if (null === $storedObject) {
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
}
return [
'isPresent' => true,
'title' => $storedObject->getTitle(),
'html' => $this->twig->render(
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
),
];
}
}

View File

@@ -13,10 +13,12 @@ namespace Chill\ActivityBundle\Service\GenericDoc\Providers;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
@@ -34,8 +36,47 @@ final readonly class AccompanyingPeriodActivityGenericDocProvider implements Gen
private EntityManagerInterface $em,
private Security $security,
private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository,
private ActivityRepository $activityRepository,
) {}
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
{
if (null === $activity = $this->getRelatedEntity($genericDocDTO->key, $genericDocDTO->identifiers)) {
return null;
}
return $activity->getDocuments()->findFirst(fn (int $key, StoredObject $storedObject) => $storedObject->getId() === $genericDocDTO->identifiers['id']);
}
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
{
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
}
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
{
return self::KEY === $key && array_key_exists('activity_id', $identifiers);
}
private function getRelatedEntity(string $key, array $identifiers): ?Activity
{
return $this->activityRepository->find($identifiers['activity_id']);
}
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
{
if (null === $activity = $this->getRelatedEntity($key, $identifiers)) {
return null;
}
return new GenericDocDTO(
self::KEY,
$identifiers,
\DateTimeImmutable::createFromInterface($activity->getDate()),
$activity->getAccompanyingPeriod(),
);
}
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
{
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);

View File

@@ -18,6 +18,9 @@ use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
/**
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
*/
final readonly class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface
{
public function __construct(private StoredObjectRepository $objectRepository, private ActivityRepository $activityRepository) {}
@@ -29,7 +32,8 @@ final readonly class AccompanyingPeriodActivityGenericDocRenderer implements Gen
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
{
return '@ChillActivity/GenericDoc/activity_document.html.twig';
return ($options['row-only'] ?? false) ? '@ChillActivity/GenericDoc/activity_document_row.html.twig' :
'@ChillActivity/GenericDoc/activity_document.html.twig';
}
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
@@ -38,6 +42,7 @@ final readonly class AccompanyingPeriodActivityGenericDocRenderer implements Gen
'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']),
'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']),
'context' => $genericDocDTO->getContext(),
'show_actions' => $options['show-actions'] ?? true,
];
}
}

View File

@@ -14,3 +14,5 @@ export:
describe_action_with_subject: >-
Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date}, et un de ces sujets choisis: {reasons}
activity:
title: Échange du {date, date, long} - {type}

View File

@@ -101,6 +101,33 @@ activity:
Insert a document: Insérer un document
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

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Repository;
use Chill\CalendarBundle\Entity\CalendarDoc;
use Chill\DocStoreBundle\Entity\StoredObject;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
@@ -49,4 +50,21 @@ class CalendarDocRepository implements ObjectRepository, CalendarDocRepositoryIn
{
return CalendarDoc::class;
}
/**
* @param StoredObject|int $storedObject the StoredObject instance, or the id of the stored object
*/
public function findOneByStoredObject(StoredObject|int $storedObject): ?CalendarDoc
{
$storedObjectId = $storedObject instanceof StoredObject ? $storedObject->getId() : $storedObject;
$qb = $this->repository->createQueryBuilder('c');
$qb->where(
$qb->expr()->eq(':storedObject', 'c.storedObject')
);
$qb->setParameter('storedObject', $storedObjectId);
return $qb->getQuery()->getOneOrNullResult();
}
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Repository;
use Chill\CalendarBundle\Entity\CalendarDoc;
use Chill\DocStoreBundle\Entity\StoredObject;
interface CalendarDocRepositoryInterface
{
@@ -29,5 +30,7 @@ interface CalendarDocRepositoryInterface
public function findOneBy(array $criteria): ?CalendarDoc;
public function findOneByStoredObject(StoredObject|int $storedObject): ?CalendarDoc;
public function getClassName();
}

View File

@@ -1,5 +1,6 @@
@import '~ChillPersonAssets/chill/scss/mixins.scss';
@import '~ChillMainAssets/module/bootstrap/shared';
@import '~ChillPersonAssets/chill/scss/mixins.scss';
@import 'bootstrap/scss/_badge.scss';
.badge-calendar {
display: inline-block;
@@ -23,3 +24,35 @@
}
}
.badge-calendar-simple {
@extend .badge;
display: inline-block;
margin: 0.2rem 0;
padding-left: 0;
padding-right: 0.5rem;
border-left: 20px groove $chill-l-gray;
border-radius: $badge-border-radius;
max-width: 100%;
background-color: $gray-100;
color: black;
font-weight: normal;
overflow: hidden;
font-weight: normal;
font-size: unset;
text-overflow: ellipsis;
text-indent: 5px hanging;
text-align: left;
&::before {
margin-right: 3px;
position: relative;
left: -0.5px;
font-family: ForkAwesome;
content: '\f04b';
color: $chill-l-gray;
}
}

View File

@@ -16,7 +16,7 @@ div.calendar-list {
}
& > a.calendar-list__global {
display: inline-block;;
display: inline-block;
padding: 0.2rem;
min-width: 2rem;
border: 1px solid var(--bs-chill-blue);

View File

@@ -96,23 +96,23 @@
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="arg: EventApi">
<span :class="eventClasses(arg.event)">
<b v-if="arg.event.extendedProps.is === 'remote'">{{
arg.event.title
<template v-slot:eventContent="{ event }">
<span :class="eventClasses(event)">
<b v-if="event.extendedProps.is === 'remote'">{{
event.title
}}</b>
<b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.timeText }} -
{{ arg.event.extendedProps.locationName }}</b
<b v-else-if="event.extendedProps.is === 'range'"
>{{ formatDate(event.startStr) }} -
{{ event.extendedProps.locationName }}</b
>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title
<b v-else-if="event.extendedProps.is === 'local'">{{
event.title
}}</b>
<b v-else>no 'is'</b>
<a
v-if="arg.event.extendedProps.is === 'range'"
v-if="event.extendedProps.is === 'range'"
class="fa fa-fw fa-times delete"
@click.prevent="onClickDelete(arg.event)"
@click.prevent="onClickDelete(event)"
>
</a>
</span>
@@ -221,13 +221,12 @@ import type {
DatesSetArg,
EventInput,
} from "@fullcalendar/core";
import { reactive, computed, ref, onMounted } from "vue";
import { computed, ref, onMounted } from "vue";
import { useStore } from "vuex";
import { key } from "./store";
import FullCalendar from "@fullcalendar/vue3";
import frLocale from "@fullcalendar/core/locales/fr";
import interactionPlugin, {
DropArg,
EventResizeDoneArg,
} from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
@@ -237,19 +236,13 @@ import {
EventDropArg,
EventClickArg,
} from "@fullcalendar/core";
import {
dateToISO,
ISOToDate,
} from "../../../../../ChillMainBundle/Resources/public/chill/js/date";
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
import VueMultiselect from "vue-multiselect";
import { Location } from "../../../../../ChillMainBundle/Resources/public/types";
import { Location } from "ChillMainAssets/types";
import EditLocation from "./Components/EditLocation.vue";
import { useI18n } from "vue-i18n";
const store = useStore(key);
const { t } = useI18n();
const showWeekends = ref(false);
const slotDuration = ref("00:15:00");
const slotMinTime = ref("09:00:00");
@@ -301,6 +294,11 @@ const nextWeeks = computed((): Weeks[] =>
}),
);
const formatDate = (datetime: string) => {
console.log(typeof datetime);
return ISOToDate(datetime);
};
const baseOptions = ref<CalendarOptions>({
locale: frLocale,
plugins: [interactionPlugin, timeGridPlugin],
@@ -353,7 +351,7 @@ const pickedLocation = computed<Location | null>({
* return the show classes for the event
* @param arg
*/
const eventClasses = function (arg: EventApi): object {
const eventClasses = function (): object {
return { calendarRangeItems: true };
};
@@ -431,7 +429,6 @@ function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
if (payload.event.extendedProps.is !== "range") {
return;
}
const changedEvent = payload.event;
store.dispatch("calendarRanges/patchRangeTime", {
calendarRangeId: payload.event.extendedProps.calendarRangeId,

View File

@@ -106,7 +106,10 @@ export default {
});
state.key = state.key + toAdd.length;
},
addExternals(state, externalEvents: (EventInput & { id: string })[]) {
addExternals(
state: CalendarRangesState,
externalEvents: (EventInput & { id: string })[],
) {
const toAdd = externalEvents.filter(
(r) => !state.rangesIndex.has(r.id),
);
@@ -160,7 +163,7 @@ export default {
state.key = state.key + 1;
}
},
updateRange(state, range: CalendarRange) {
updateRange(state: CalendarRangesState, range: CalendarRange) {
const found = state.ranges.find(
(r) => r.calendarRangeId === range.id && r.is === "range",
);
@@ -207,7 +210,7 @@ export default {
});
},
createRange(
ctx,
ctx: Context,
{
start,
end,
@@ -253,10 +256,10 @@ export default {
throw error;
});
},
deleteRange(ctx, calendarRangeId: number) {
deleteRange(ctx: Context, calendarRangeId: number) {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
makeFetch<undefined, never>("DELETE", url).then((_) => {
makeFetch<undefined, never>("DELETE", url).then(() => {
ctx.commit("removeRange", calendarRangeId);
});
},
@@ -347,10 +350,10 @@ export default {
);
}
return Promise.all(promises).then((_) => Promise.resolve(null));
return Promise.all(promises).then(() => Promise.resolve(null));
},
copyFromWeekToAnotherWeek(
ctx,
ctx: Context,
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
@@ -371,7 +374,7 @@ export default {
);
}
return Promise.all(promises).then((_) => Promise.resolve(null));
return Promise.all(promises).then(() => Promise.resolve(null));
},
},
} as Module<CalendarRangesState, State>;

View File

@@ -5,71 +5,5 @@
{% set c = document.calendar %}
<div class="item-bloc">
<div class="item-row">
<div class="item-col" style="width: unset">
{% if document.storedObject.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.storedObject.isFailure %}
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
{% endif %}
<div>
{% if c.accompanyingPeriod is not null and context == 'person' %}
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
</span>&nbsp;
{% endif %}
<span class="badge-calendar">
<span class="title_label"></span>
<span class="title_action">
{{ 'Calendar'|trans }}
{% if c.endDate.diff(c.startDate).days >= 1 %}
{{ c.startDate|format_datetime('short', 'short') }}
- {{ c.endDate|format_datetime('short', 'short') }}
{% else %}
{{ c.startDate|format_datetime('short', 'short') }}
- {{ c.endDate|format_datetime('none', 'short') }}
{% endif %}
</span>
</span>
</div>
<div class="denomination h2">
{{ document.storedObject.title|chill_print_or_message("No title") }}
</div>
{% if document.storedObject.hasTemplate %}
<div>
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
</div>
{% endif %}
</div>
<div class="item-col">
<div class="container">
<div class="dates row text-end">
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
</div>
</div>
</div>
</div>
<div class="item-row separator">
<div class="item-col item-meta">
{{ mmm.createdBy(document) }}
</div>
<ul class="item-col record_actions flex-shrink-1">
{% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
<li>
{{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }}
</li>
{% endif %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', {'id': c.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
</li>
{% endif %}
</ul>
</div>
{{ include('@ChillCalendar/GenericDoc/calendar_document_row.html.twig') }}
</div>

View File

@@ -0,0 +1,73 @@
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
{% set c = document.calendar %}
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
{% if document.storedObject.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.storedObject.isFailure %}
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
{% endif %}
<div>
<span class="badge-calendar-simple">
{{ 'Calendar'|trans }}
{% if c.endDate.diff(c.startDate).days >= 1 %}
{{ c.startDate|format_datetime('short', 'short') }}
- {{ c.endDate|format_datetime('short', 'short') }}
{% else %}
{{ c.startDate|format_datetime('short', 'short') }}
- {{ c.endDate|format_datetime('none', 'short') }}
{% endif %}
</span>
</div>
<div class="denomination h2">
{{ document.storedObject.title|chill_print_or_message("No title") }}
</div>
{% if document.storedObject.hasTemplate %}
<div>
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
</div>
{% endif %}
</div>
<div class="aside">
<div class="dates row text-end">
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
</div>
{% if c.accompanyingPeriod is not null and context == 'person' %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
</span>&nbsp;
</div>
{% endif %}
</div>
</div>
</div>
{% if show_actions %}
<div class="item-row separator">
<div class="item-col item-meta">
{{ mmm.createdBy(document) }}
</div>
<ul class="item-col record_actions flex-shrink-1">
{% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
<li>
{{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }}
</li>
{% endif %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', {'id': c.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
</li>
{% endif %}
</ul>
</div>
{% endif %}

View File

@@ -0,0 +1,51 @@
<?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\CalendarBundle\Service\GenericDoc\Normalizer;
use Chill\CalendarBundle\Repository\CalendarDocRepositoryInterface;
use Chill\CalendarBundle\Service\GenericDoc\Providers\AccompanyingPeriodCalendarGenericDocProvider;
use Chill\CalendarBundle\Service\GenericDoc\Renderers\AccompanyingPeriodCalendarGenericDocRenderer;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
final readonly class AccompanyingPeriodCalendarGenericDocNormalizer implements GenericDocNormalizerInterface
{
public function __construct(
private AccompanyingPeriodCalendarGenericDocRenderer $renderer,
private CalendarDocRepositoryInterface $calendarDocRepository,
private Environment $twig,
private TranslatorInterface $translator,
) {}
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
{
return AccompanyingPeriodCalendarGenericDocProvider::KEY === $genericDocDTO->key && 'json' === $format;
}
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
{
if (null === $calendarDoc = $this->calendarDocRepository->find($genericDocDTO->identifiers['id'])) {
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
}
return [
'isPresent' => true,
'title' => $calendarDoc->getStoredObject()->getTitle(),
'html' => $this->twig->render(
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
),
];
}
}

View File

@@ -13,10 +13,12 @@ namespace Chill\CalendarBundle\Service\GenericDoc\Providers;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CalendarDoc;
use Chill\CalendarBundle\Repository\CalendarDocRepositoryInterface;
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
@@ -38,8 +40,38 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
public function __construct(
private Security $security,
private EntityManagerInterface $em,
private CalendarDocRepositoryInterface $calendarRepository,
) {}
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
{
return $this->calendarRepository->find($genericDocDTO->identifiers['id'])?->getStoredObject();
}
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
{
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
}
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
{
return self::KEY === $key && array_key_exists('id', $identifiers);
}
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
{
if (null === $calendarDoc = $this->calendarRepository->find($identifiers['id'])) {
return null;
}
return new GenericDocDTO(
self::KEY,
$identifiers,
\DateTimeImmutable::createFromInterface($calendarDoc->getCreatedAt() ?? new \DateTimeImmutable('now')),
$calendarDoc->getCalendar()->getAccompanyingPeriod() ?? $calendarDoc->getCalendar()->getPerson()
);
}
/**
* @throws MappingException
*/
@@ -82,7 +114,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
[Types::INTEGER]
);
return $query;
return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
}
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool

View File

@@ -17,6 +17,9 @@ use Chill\CalendarBundle\Service\GenericDoc\Providers\PersonCalendarGenericDocPr
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
/**
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
*/
final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRendererInterface
{
public function __construct(private CalendarDocRepository $repository) {}
@@ -28,7 +31,8 @@ final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements Gen
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
{
return '@ChillCalendar/GenericDoc/calendar_document.html.twig';
return $options['row-only'] ?? false ? '@ChillCalendar/GenericDoc/calendar_document_row.html.twig'
: '@ChillCalendar/GenericDoc/calendar_document.html.twig';
}
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
@@ -36,6 +40,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements Gen
return [
'document' => $this->repository->find($genericDocDTO->identifiers['id']),
'context' => $genericDocDTO->getContext(),
'show_actions' => $options['show-actions'] ?? true,
];
}
}

View File

@@ -0,0 +1,82 @@
<?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\DocGeneratorBundle\Tests\Controller;
use Chill\DocStoreBundle\Controller\GenericDocForAccompanyingPeriodListApiController;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\MainBundle\Pagination\Paginator;
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;
/**
* @internal
*
* @coversNothing
*/
class GenericDocForAccompanyingPeriodListApiControllerTest extends TestCase
{
public function testSmokeTest(): void
{
$accompanyingPeriod = new AccompanyingPeriod();
$docs = [
new GenericDocDTO('dummy', ['id' => 9], new \DateTimeImmutable('2024-08-01'), $accompanyingPeriod),
new GenericDocDTO('dummy', ['id' => 1], new \DateTimeImmutable('2024-09-01'), $accompanyingPeriod),
];
$manager = $this->createMock(ManagerInterface::class);
$manager->method('findDocForAccompanyingPeriod')->with($accompanyingPeriod)->willReturn($docs);
$manager->method('countDocForAccompanyingPeriod')->with($accompanyingPeriod)->willReturn(2);
$paginatorFactory = $this->createMock(PaginatorFactoryInterface::class);
$paginatorFactory->method('create')->with(2)->willReturn(new Paginator(
2,
20,
1,
'/route',
[],
$this->createMock(UrlGeneratorInterface::class),
'page',
'item-per-page'
));
$serializer = $this->createMock(SerializerInterface::class);
$serializer->method('serialize')->with($this->isInstanceOf(Collection::class))->willReturn(
json_encode(['docs' => []])
);
$security = $this->createMock(Security::class);
$security->expects($this->once())->method('isGranted')
->with(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)->willReturn(true);
$controller = new GenericDocForAccompanyingPeriodListApiController(
$manager,
$security,
$paginatorFactory,
$serializer,
);
$response = $controller($accompanyingPeriod);
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertEquals('{"docs":[]}', $response->getContent());
}
}

View File

@@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle;
use Chill\DocStoreBundle\DependencyInjection\Compiler\StorageConfigurationCompilerPass;
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
@@ -28,6 +29,8 @@ class ChillDocStoreBundle extends Bundle
->addTag('chill_doc_store.generic_doc_person_provider');
$container->registerForAutoconfiguration(GenericDocRendererInterface::class)
->addTag('chill_doc_store.generic_doc_renderer');
$container->registerForAutoconfiguration(GenericDocNormalizerInterface::class)
->addTag('chill_doc_store.generic_doc_metadata_normalizer');
$container->addCompilerPass(new StorageConfigurationCompilerPass());
}

View File

@@ -92,13 +92,14 @@ class DocumentCategoryController extends AbstractController
$nextId = $em
->createQuery(
'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c'
'SELECT (CASE WHEN MAX(c.idInsideBundle) IS NULL THEN 1 ELSE MAX(c.idInsideBundle) + 1 END)
FROM ChillDocStoreBundle:DocumentCategory c'
)
->getSingleResult();
->getSingleScalarResult();
$documentCategory = new DocumentCategory(
ChillDocStoreBundle::class,
reset($nextId)
$nextId
);
$documentCategory

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\GenericDoc\Manager;
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
@@ -25,7 +25,7 @@ final readonly class GenericDocForAccompanyingPeriodController
{
public function __construct(
private FilterOrderHelperFactory $filterOrderHelperFactory,
private Manager $manager,
private ManagerInterface $manager,
private PaginatorFactory $paginator,
private Security $security,
private \Twig\Environment $twig,
@@ -68,6 +68,9 @@ final readonly class GenericDocForAccompanyingPeriodController
);
$paginator = $this->paginator->create($nb);
// restrict the number of items for performance reasons
$paginator->setItemsPerPage(20);
$documents = $this->manager->findDocForAccompanyingPeriod(
$accompanyingPeriod,
$paginator->getCurrentPageFirstItemNumber(),

View File

@@ -0,0 +1,57 @@
<?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\DocStoreBundle\Controller;
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Provide the list of GenericDoc for an accompanying period.
*/
final readonly class GenericDocForAccompanyingPeriodListApiController
{
public function __construct(
private ManagerInterface $manager,
private Security $security,
private PaginatorFactoryInterface $paginator,
private SerializerInterface $serializer,
) {}
#[Route('/api/1.0/doc-store/generic-doc/by-period/{id}/index', methods: ['GET'])]
public function __invoke(AccompanyingPeriod $accompanyingPeriod): JsonResponse
{
if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)) {
throw new AccessDeniedHttpException('not allowed to see the documents for accompanying period');
}
$nb = $this->manager->countDocForAccompanyingPeriod($accompanyingPeriod);
$paginator = $this->paginator->create($nb);
$docs = $this->manager->findDocForAccompanyingPeriod($accompanyingPeriod, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage());
$collection = new Collection($docs, $paginator);
return new JsonResponse(
$this->serializer->serialize($collection, 'json', [AbstractNormalizer::GROUPS => ['read']]),
json: true,
);
}
}

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\GenericDoc\Manager;
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
@@ -25,7 +25,7 @@ final readonly class GenericDocForPerson
{
public function __construct(
private FilterOrderHelperFactory $filterOrderHelperFactory,
private Manager $manager,
private ManagerInterface $manager,
private PaginatorFactory $paginator,
private Security $security,
private \Twig\Environment $twig,

View File

@@ -46,9 +46,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
#[ORM\ManyToOne(targetEntity: DocGeneratorTemplate::class)]
private ?DocGeneratorTemplate $template = null;
#[Assert\Length(min: 2, max: 250)]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
private string $title = '';
/**
* Store the title of the document, if the title is set before the document.
*/
private string $proxyTitle = '';
#[ORM\ManyToOne(targetEntity: \Chill\MainBundle\Entity\User::class)]
private ?\Chill\MainBundle\Entity\User $user = null;
@@ -78,9 +79,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
return $this->template;
}
#[Assert\Length(min: 2, max: 250)]
public function getTitle(): string
{
return $this->title;
return (string) $this->getObject()?->getTitle();
}
public function getUser()
@@ -113,6 +115,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
{
$this->object = $object;
if ('' !== $this->proxyTitle) {
$this->object->setTitle($this->proxyTitle);
}
return $this;
}
@@ -125,7 +131,11 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
public function setTitle(string $title): self
{
$this->title = $title;
if (null !== $this->getObject()) {
$this->getObject()->setTitle($title);
} else {
$this->proxyTitle = $title;
}
return $this;
}

View File

@@ -0,0 +1,20 @@
<?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\DocStoreBundle\GenericDoc\Exception;
class AssociatedStoredObjectNotFound extends \RuntimeException
{
public function __construct(string $key, array $identifiers, int $code = 0, ?\Throwable $previous = null)
{
parent::__construct(sprintf('No stored object found for generic doc with key "%s" and identifiers "%s"', $key, json_encode($identifiers)), $code, $previous);
}
}

View File

@@ -0,0 +1,14 @@
<?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\DocStoreBundle\GenericDoc\Exception;
class NotNormalizableGenericDocException extends \LogicException {}

View File

@@ -0,0 +1,14 @@
<?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\DocStoreBundle\GenericDoc\Exception;
class UnexpectedValueException extends \UnexpectedValueException {}

View File

@@ -13,7 +13,7 @@ namespace Chill\DocStoreBundle\GenericDoc;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
interface GenericDocForAccompanyingPeriodProviderInterface
interface GenericDocForAccompanyingPeriodProviderInterface extends GenericDocProviderInterface
{
public function buildFetchQueryForAccompanyingPeriod(
AccompanyingPeriod $accompanyingPeriod,

View File

@@ -0,0 +1,30 @@
<?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\DocStoreBundle\GenericDoc;
/**
* Normalize a Generic Doc.
*/
interface GenericDocNormalizerInterface
{
/**
* Return true if a generic doc can be normalized by this implementation.
*/
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool;
/**
* Normalize a generic doc into an array.
*
* @return array{title: string, html?: string, isPresent: bool}
*/
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array;
}

View File

@@ -0,0 +1,38 @@
<?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\DocStoreBundle\GenericDoc;
use Chill\DocStoreBundle\Entity\StoredObject;
interface GenericDocProviderInterface
{
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject;
/**
* Return true if this provider supports the given Generic doc for various informations.
*
* Concerned:
*
* - @see{self::fetchAssociatedStoredObject}
*/
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool;
/**
* return true if the implementation supports key and identifiers.
*/
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool;
/**
* Build a GenericDocDTO, given the key and identifiers.
*/
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO;
}

View File

@@ -11,13 +11,16 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\GenericDoc;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
use Chill\DocStoreBundle\GenericDoc\Exception\NotNormalizableGenericDocException;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Types\Types;
final readonly class Manager
final readonly class Manager implements ManagerInterface
{
private FetchQueryToSqlBuilder $builder;
@@ -31,16 +34,16 @@ final readonly class Manager
* @var iterable<GenericDocForPersonProviderInterface>
*/
private iterable $providersForPerson,
/**
* @var iterable<GenericDocNormalizerInterface>
*/
private iterable $genericDocNormalizers,
private Connection $connection,
) {
$this->builder = new FetchQueryToSqlBuilder();
}
/**
* @param list<string> $places
*
* @throws Exception
*/
public function countDocForAccompanyingPeriod(
AccompanyingPeriod $accompanyingPeriod,
?\DateTimeImmutable $startDate = null,
@@ -83,13 +86,6 @@ final readonly class Manager
return $this->countDoc($sql, $params, $types);
}
/**
* @param list<string> $places places to search. When empty, search in all places
*
* @return iterable<GenericDocDTO>
*
* @throws Exception
*/
public function findDocForAccompanyingPeriod(
AccompanyingPeriod $accompanyingPeriod,
int $offset = 0,
@@ -129,10 +125,35 @@ final readonly class Manager
}
/**
* @param list<string> $places places to search. When empty, search in all places
* Fetch a generic doc, if it does exists.
*
* @return iterable<GenericDocDTO>
* Currently implemented only on generic docs linked with accompanying period
*/
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
{
foreach ($this->providersForAccompanyingPeriod as $provider) {
if ($provider->supportsKeyAndIdentifiers($key, $identifiers)) {
return $provider->buildOneGenericDoc($key, $identifiers);
}
}
return null;
}
/**
* @throws AssociatedStoredObjectNotFound if no stored object can be found
*/
public function fetchStoredObject(GenericDocDTO $genericDocDTO): StoredObject
{
foreach ($this->providersForAccompanyingPeriod as $provider) {
if ($provider->supportsGenericDoc($genericDocDTO)) {
return $provider->fetchAssociatedStoredObject($genericDocDTO);
}
}
throw new AssociatedStoredObjectNotFound($genericDocDTO->key, $genericDocDTO->identifiers);
}
public function findDocForPerson(
Person $person,
int $offset = 0,
@@ -161,6 +182,28 @@ final readonly class Manager
return $this->places($sql, $params, $types);
}
public function isGenericDocNormalizable(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
{
foreach ($this->genericDocNormalizers as $genericDocNormalizer) {
if ($genericDocNormalizer->supportsNormalization($genericDocDTO, $format, $context)) {
return true;
}
}
return false;
}
public function normalizeGenericDoc(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
{
foreach ($this->genericDocNormalizers as $genericDocNormalizer) {
if ($genericDocNormalizer->supportsNormalization($genericDocDTO, $format, $context)) {
return $genericDocNormalizer->normalize($genericDocDTO, $format, $context);
}
}
throw new NotNormalizableGenericDocException();
}
private function places(string $sql, array $params, array $types): array
{
if ('' === $sql) {

View File

@@ -0,0 +1,64 @@
<?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\DocStoreBundle\GenericDoc;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\Exception;
interface ManagerInterface
{
/**
* @param list<string> $places
*
* @throws Exception
*/
public function countDocForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): int;
public function countDocForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): int;
/**
* @param list<string> $places places to search. When empty, search in all places
*
* @return iterable<GenericDocDTO>
*
* @throws Exception
*/
public function findDocForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, int $offset = 0, int $limit = 20, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): iterable;
/**
* @param list<string> $places places to search. When empty, search in all places
*
* @return iterable<GenericDocDTO>
*/
public function findDocForPerson(Person $person, int $offset = 0, int $limit = 20, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): iterable;
public function placesForPerson(Person $person): array;
public function placesForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): array;
public function isGenericDocNormalizable(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool;
/**
* @return array{title: string, html?: string}
*/
public function normalizeGenericDoc(GenericDocDTO $genericDocDTO, string $format, array $context = []): array;
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO;
/**
* @throws AssociatedStoredObjectNotFound if no stored object can be found
*/
public function fetchStoredObject(GenericDocDTO $genericDocDTO): StoredObject;
}

View File

@@ -0,0 +1,56 @@
<?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\DocStoreBundle\GenericDoc\Normalizer;
use Chill\DocStoreBundle\GenericDoc\Exception\UnexpectedValueException;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
use Chill\DocStoreBundle\GenericDoc\Renderer\AccompanyingCourseDocumentGenericDocRenderer;
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
use Twig\Environment;
class AccompanyingCourseDocumentGenericDocNormalizer implements GenericDocNormalizerInterface
{
public function __construct(
private readonly AccompanyingCourseDocumentRepository $repository,
private readonly Environment $twig,
private readonly AccompanyingCourseDocumentGenericDocRenderer $renderer,
) {}
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
{
return AccompanyingCourseDocumentGenericDocProvider::KEY === $genericDocDTO->key;
}
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
{
if (!array_key_exists('id', $genericDocDTO->identifiers)) {
throw new UnexpectedValueException('key id not found in identifier');
}
$document = $this->repository->find($genericDocDTO->identifiers['id']);
if (null === $document) {
throw new UnexpectedValueException('document not found with id '.$genericDocDTO->identifiers['id']);
}
return [
'isPresent' => true,
'title' => $document->getTitle(),
'html' => $this->twig->render(
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
),
];
}
}

View File

@@ -0,0 +1,51 @@
<?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\DocStoreBundle\GenericDoc\Normalizer;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
use Chill\DocStoreBundle\GenericDoc\Renderer\AccompanyingCourseDocumentGenericDocRenderer;
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
final readonly class PersonDocumentGenericDocNormalizer implements GenericDocNormalizerInterface
{
public function __construct(
private PersonDocumentRepository $personDocumentRepository,
private AccompanyingCourseDocumentGenericDocRenderer $renderer,
private Environment $twig,
private TranslatorInterface $translator,
) {}
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
{
return PersonDocumentGenericDocProvider::KEY === $genericDocDTO->key && 'json' === $format;
}
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
{
if (null === $personDocument = $this->personDocumentRepository->find($genericDocDTO->identifiers['id'])) {
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
}
return [
'isPresent' => true,
'title' => $personDocument->getTitle(),
'html' => $this->twig->render(
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
),
];
}
}

View File

@@ -12,10 +12,13 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\GenericDoc\Providers;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
@@ -31,17 +34,47 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
public function __construct(
private Security $security,
private EntityManagerInterface $entityManager,
private AccompanyingCourseDocumentRepository $accompanyingCourseDocumentRepository,
) {}
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
{
return $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id'])?->getObject();
}
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
{
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
}
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
{
return self::KEY === $key;
}
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
{
if (null === $accompanyingCourseDocument = $this->accompanyingCourseDocumentRepository->find($identifiers['id'])) {
return null;
}
return new GenericDocDTO(
self::KEY,
$identifiers,
\DateTimeImmutable::createFromInterface($accompanyingCourseDocument->getDate()),
$accompanyingCourseDocument->getCourse(),
);
}
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
{
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
$query = new FetchQuery(
self::KEY,
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
sprintf('jsonb_build_object(\'id\', acc_course_document.%s)', $classMetadata->getIdentifierColumnNames()[0]),
$classMetadata->getColumnName('date'),
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName()
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS acc_course_document'
);
$query->addWhereClause(
@@ -64,7 +97,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
$query = new FetchQuery(
self::KEY,
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
sprintf('jsonb_build_object(\'id\', acc_course_document.%s)', $classMetadata->getIdentifierColumnNames()[0]),
$classMetadata->getColumnName('date'),
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS acc_course_document'
);
@@ -110,6 +143,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
private function addWhereClause(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
{
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
$storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class);
if (null !== $startDate) {
$query->addWhereClause(
@@ -128,9 +162,19 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
}
if (null !== $content and '' !== $content) {
// add join clause to stored_object table
$query->addJoinClause(
sprintf(
'JOIN %s AS doc_store ON doc_store.%s = acc_course_document.%s',
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
$storedObjectMetadata->getSingleIdentifierColumnName(),
$classMetadata->getSingleAssociationJoinColumnName('object')
)
);
$query->addWhereClause(
sprintf(
'(%s ilike ? OR %s ilike ?)',
'(doc_store.%s ilike ? OR acc_course_document.%s ilike ?)',
$classMetadata->getColumnName('title'),
$classMetadata->getColumnName('description')
),

View File

@@ -11,10 +11,13 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\GenericDoc\Providers;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
@@ -27,8 +30,38 @@ final readonly class PersonDocumentGenericDocProvider implements GenericDocForPe
public function __construct(
private Security $security,
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository,
private PersonDocumentRepository $personDocumentRepository,
) {}
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
{
return $this->personDocumentRepository->find($genericDocDTO->identifiers['id'])?->getObject();
}
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
{
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
}
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
{
return self::KEY === $key && array_key_exists('id', $identifiers);
}
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
{
if (null === $document = $this->personDocumentRepository->find($identifiers['id'])) {
return null;
}
return new GenericDocDTO(
self::KEY,
$identifiers,
\DateTimeImmutable::createFromInterface($document->getDate()),
$document->getPerson()
);
}
public function buildFetchQueryForPerson(
Person $person,
?\DateTimeImmutable $startDate = null,

View File

@@ -18,6 +18,9 @@ use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericD
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
/**
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
*/
final readonly class AccompanyingCourseDocumentGenericDocRenderer implements GenericDocRendererInterface
{
public function __construct(
@@ -33,6 +36,10 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
{
if ($options['row-only'] ?? false) {
return '@ChillDocStore/List/list_item_row.html.twig';
}
return '@ChillDocStore/List/list_item.html.twig';
}
@@ -44,6 +51,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
'accompanyingCourse' => $doc->getCourse(),
'options' => $options,
'context' => $genericDocDTO->getContext(),
'show_actions' => $options['show-actions'] ?? true,
];
}
@@ -53,6 +61,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
'person' => $doc->getPerson(),
'options' => $options,
'context' => $genericDocDTO->getContext(),
'show_actions' => $options['show-actions'] ?? true,
];
}
}

View File

@@ -13,11 +13,25 @@ namespace Chill\DocStoreBundle\GenericDoc\Twig;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
/**
* Render a generic doc, to display it into a page.
*
* @template T of array
*/
interface GenericDocRendererInterface
{
/**
* @param T $options the options defined by the renderer
*/
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool;
/**
* @param T $options the options defined by the renderer
*/
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string;
/**
* @param T $options the options defined by the renderer
*/
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array;
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Repository;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
@@ -136,6 +137,7 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
private function addFilterClauses(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
{
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
if (null !== $startDate) {
$query->addWhereClause(
@@ -154,10 +156,20 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
}
if (null !== $content and '' !== $content) {
$query->addJoinClause(
sprintf(
'JOIN %s AS doc_store ON doc_store.%s = person_document.%s',
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
$storedObjectMetadata->getSingleIdentifierColumnName(),
$personDocMetadata->getSingleAssociationJoinColumnName('object')
)
);
$query->addWhereClause(
sprintf(
'(%s ilike ? OR %s ilike ?)',
$personDocMetadata->getColumnName('title'),
'(doc_store.%s ilike ? OR person_document.%s ilike ?)',
$storedObjectMetadata->getColumnName('title'),
$personDocMetadata->getColumnName('description')
),
['%'.$content.'%', '%'.$content.'%'],

View File

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

View File

@@ -10,6 +10,9 @@ const startApp = (
collectionEntry: null | HTMLLIElement,
): void => {
console.log("app started", divElement);
const inputTitle = collectionEntry?.querySelector("input[type='text']");
const input_stored_object: HTMLInputElement | null =
divElement.querySelector("input[data-stored-object]");
if (null === input_stored_object) {
@@ -26,9 +29,10 @@ const startApp = (
const app = createApp({
template:
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
data(vm) {
data() {
return {
existingDoc: existingDoc,
inputTitle: inputTitle,
};
},
components: {
@@ -38,10 +42,13 @@ const startApp = (
addDocument: function ({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void {
stored_object.title = file_name;
console.log("object added", stored_object);
console.log("version added", stored_object_version);
this.$data.existingDoc = stored_object;
@@ -49,6 +56,11 @@ const startApp = (
input_stored_object.value = JSON.stringify(
this.$data.existingDoc,
);
if (this.$data.inputTitle) {
if (!this.$data.inputTitle?.value) {
this.$data.inputTitle.value = file_name;
}
}
},
removeDocument: function (object: StoredObject): void {
console.log("catch remove document", object);

View File

@@ -1,4 +1,4 @@
import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
import { createApp } from "vue";
import { StoredObject, StoredObjectStatusChange } from "../../types";

View File

@@ -0,0 +1,71 @@
import { DateTime } from "ChillMainAssets/types";
import { StoredObject } from "ChillDocStoreAssets/types/index";
export interface GenericDocMetadata {
isPresent: boolean;
}
/**
* Empty metadata for a GenericDoc
*/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface EmptyMetadata extends GenericDocMetadata {}
/**
* Minimal Metadata for a GenericDoc with a normalizer
*/
export interface BaseMetadata extends GenericDocMetadata {
title: string;
}
/**
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
*/
export interface GenericDoc {
type: "doc_store_generic_doc";
uniqueKey: string;
key: string;
identifiers: object;
context: "person" | "accompanying-period";
doc_date: DateTime;
metadata: GenericDocMetadata;
storedObject: StoredObject | null;
}
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
context: "accompanying-period";
}
interface BaseMetadataWithHtml extends BaseMetadata {
html: string;
}
export interface GenericDocForAccompanyingCourseDocument
extends GenericDocForAccompanyingPeriod {
key: "accompanying_course_document";
metadata: BaseMetadataWithHtml;
}
export interface GenericDocForAccompanyingCourseActivityDocument
extends GenericDocForAccompanyingPeriod {
key: "accompanying_course_activity_document";
metadata: BaseMetadataWithHtml;
}
export interface GenericDocForAccompanyingCourseCalendarDocument
extends GenericDocForAccompanyingPeriod {
key: "accompanying_course_calendar_document";
metadata: BaseMetadataWithHtml;
}
export interface GenericDocForAccompanyingCoursePersonDocument
extends GenericDocForAccompanyingPeriod {
key: "person_document";
metadata: BaseMetadataWithHtml;
}
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
extends GenericDocForAccompanyingPeriod {
key: "accompanying_period_work_evaluation_document";
metadata: BaseMetadataWithHtml;
}

View File

@@ -1,8 +1,5 @@
import {
DateTime,
User,
} from "../../../ChillMainBundle/Resources/public/types";
import { SignedUrlGet } from "./vuejs/StoredObjectButton/helpers";
import { DateTime, User } from "ChillMainAssets/types";
import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpers";
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
@@ -138,3 +135,10 @@ export interface ZoomLevel {
nl?: string;
};
}
export interface GenericDoc {
type: "doc_store_generic_doc";
key: string;
context: "person" | "accompanying-period";
doc_date: DateTime;
}

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