Compare commits

...

222 Commits

Author SHA1 Message Date
66f7ef8c51 prepare for beta2 release 2022-09-20 12:00:01 +02:00
cdb9d967ff [person] Feature: on evaluation, add an url field on the admin 2022-09-20 11:50:01 +02:00
29b24fa506 [Budget] Feature: allow to desactive resources and charges in the
configuration

A new key `active` (default `true`) allow to activate or desactivate the
line in the resource or charge.
2022-09-20 11:26:05 +02:00
4d192d748f [skeleton] we do not commit composer into repository 2022-09-20 10:03:05 +02:00
6bd0bcff6e Fix: Workflow: the notification is send when the user is added on first
step.

See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/813
2022-09-19 21:27:07 +02:00
5b148f967f Merge branch 'feature/load-address-bano' into 'master'
Load addresses from bano and best address

See merge request Chill-Projet/chill-bundles!448
2022-09-19 11:51:21 +00:00
8129739d27 test for postal code base importer 2022-09-17 10:59:04 +02:00
658e846120 add test for AddressReferenceBaseImporter 2022-09-17 10:44:26 +02:00
0f63548d5a import addresses and postal codes from bestaddress 2022-09-17 09:10:28 +02:00
84cda8845d Feature: command to load addresses from France from bano.openstreetmap.fr 2022-09-17 09:10:28 +02:00
9b3b9f2552 Fixed: possible unexisting variable 2022-09-17 09:10:28 +02:00
58ddf9038d Feature: load french postal code from laposte hexasmal open data 2022-09-17 09:10:28 +02:00
a9b354a6f5 Feature: add constraint to ensure postal code uniqueness and track creation and update of postal code 2022-09-17 09:10:28 +02:00
62ff4998a0 Fixed: annotation schema for ManyToMany relationship between Evaluation and SocialAction
Before this commit, the owning side of the relationship between Evaluation and SocialAction was declared twice.
2022-09-17 09:10:28 +02:00
6c191f584f fix bug when member is non-positionné, case not taken into account in if-condition 2022-09-14 12:09:25 +00:00
71f49db2f4 disable unstable filters/aggregators 2022-09-13 21:05:22 +02:00
0f0ec10857 fix rst syntax 2022-09-07 12:18:07 +02:00
da2d68a45d [documentation] new how-to to switch into branch, and add info about gitlab tokens 2022-09-06 15:25:15 +02:00
6dbee02d82 Merge branch 'master' into social_action_exports 2022-08-30 13:23:41 +02:00
7eb75b709c Merge branch '111_exports' into social_action_exports 2022-08-30 13:17:37 +02:00
72b5fcf998 minor changes 2022-08-30 13:14:40 +02:00
nobohan
bf44b6b90d export: geographical unit filter 2022-08-29 16:22:36 +02:00
nobohan
81f04d0184 export: add a geographical unit entity (migration file) 2022-08-29 15:34:33 +02:00
nobohan
f99f6d5240 export: add a geographical unit entity 2022-08-29 15:21:05 +02:00
b5139ec460 exports: improve activity DateAggregator
- put orderby at the end
- addSelect with TO_CHAR() function (cancel EXRACT())
- uniformize addSelect cases
- orderBy with variable
- improve getLabels
2022-08-25 14:58:25 +02:00
f5baa2c152 exports: DurationAggregator with approximative(*) date interval
(*) for more precise, we need dql interval function
2022-08-25 13:49:12 +02:00
e1ec2dc25c exports: DurationAggregator: test qb addSelect for compute date interval 2022-08-25 13:49:12 +02:00
2c151c2ec9 exports: add new duration aggregator (wip) 2022-08-25 13:49:12 +02:00
cf642d2783 month year aggregator working 2022-08-25 13:08:24 +02:00
f3ceee7485 Merge branch '111_exports' into social_action_exports 2022-08-25 09:28:01 +02:00
9854fb0664 cancel reason aggregator + start of monthYearAggregator 2022-08-25 09:27:30 +02:00
7173e4be4a exports: fix activity DateAggregator; add customs DQL Date/Time functions 2022-08-24 15:44:18 +02:00
52902e905a exports: test all activity filters/aggregators and fix errors 2022-08-24 11:36:54 +02:00
dea7982ca8 exports: add activity linked_to_acp new aggregators (wip) 2022-08-22 19:04:48 +02:00
c4944cce17 exports: move activity old aggregators in PersonAggregators folder 2022-08-22 17:12:33 +02:00
f8f17a6a06 exports: move activity old filters in PersonFilters folder 2022-08-22 13:37:20 +02:00
95ea3bf30e refactor builder form in a filter 2022-08-22 13:29:22 +02:00
248d56e598 exports: add activity linked_to_acp new filters 2022-08-22 13:28:50 +02:00
9af620ece5 fix translations 2022-08-22 10:17:44 +02:00
4a1615afd5 export, fix merge translations 2022-08-22 10:02:38 +02:00
2bf5e934e9 merge 111_exports 2022-08-18 12:19:01 +02:00
03d098e5e1 aggregators added - monthYearAggregator not finished 2022-08-18 12:15:30 +02:00
1de853a88b fix count appointment indicator 2022-08-18 10:58:05 +02:00
3eb07121ce filters added 2022-08-18 10:57:15 +02:00
05fd35cfb4 all indicators added 2022-08-18 10:56:22 +02:00
e357899d78 Merge branch 'social_action_exports' into 111_exports 2022-08-17 17:48:43 +02:00
62562cc57e rename querybuilder calendar 2022-08-17 17:37:05 +02:00
dfbaaef778 add average duration indicator calendar 2022-08-17 17:36:42 +02:00
d04d758f27 exports: activityDate filter enable to activity linked to acl 2022-08-17 17:14:24 +02:00
c199bb5534 exports: activity improve translations 2022-08-17 17:02:21 +02:00
26cd677501 exports: activity new indicators works + improve translations 2022-08-17 16:07:20 +02:00
3c5d533c58 first indicator for calendar exports 2022-08-17 16:04:46 +02:00
5060906591 more attempts to make dynamic form work 2022-08-17 16:03:58 +02:00
c17036fcda exports: fix activitytype filter/aggregator appliedTo activity_person and too activity_acl 2022-08-17 15:28:51 +02:00
c09c7a9615 exports: add new modifiers declarations to split filter/aggregators between activities linked to person <> acp 2022-08-17 14:58:20 +02:00
df9a5071c7 exports: display group as a breadcrumb in template
add a private method in controller which could be moved maybe in ExportManager
2022-08-17 14:56:24 +02:00
01f6eb1a8f fix merge conflict in translations 2022-08-17 11:57:46 +02:00
27c2fb1c1f merge with 111_exports 2022-08-17 11:56:22 +02:00
d9b668e614 exports: create new activity linked to acp exports indicators + title/descr/groups translations 2022-08-17 11:44:15 +02:00
57d00df460 exports: split namespace between activity linked to person <> acp 2022-08-17 10:12:29 +02:00
94cee14a11 exports: complete modifiers 2022-08-17 09:50:35 +02:00
4ebe064538 exports: improve translations 2022-08-17 09:49:33 +02:00
fb18d9fb56 exports: improve activity translations 2022-08-16 17:12:35 +02:00
b2c1c0ec76 add filter residential address at thirdparty of category 2022-08-16 16:19:39 +02:00
42122f5832 exports: use Declarations in activity exports 2022-08-16 15:37:54 +02:00
8093c532d1 exports: overwrite activity translations 2022-08-16 15:30:12 +02:00
2c53f92a2e small refactorisations 2022-08-16 15:13:47 +02:00
4c91bf5084 household position aggregator for person 2022-08-16 15:07:03 +02:00
c2c409998b exports: precise translation when acp filters/aggreg are combined with others 2022-08-16 14:52:53 +02:00
bde623e806 add second indicator for person exports 2022-08-16 10:47:36 +02:00
a9e83d3dc8 delete double translations 2022-08-16 10:42:13 +02:00
afbedf3a4a Merge branch '111_exports' into social_action_exports 2022-08-16 10:19:04 +02:00
4e12684f98 add condition on join clause to improve combined Filters/Aggregators 2022-08-11 18:09:25 +02:00
619ae4e458 improve social work actions translations 2022-08-11 17:43:20 +02:00
b03950d0f7 improve translations for treating agent 2022-08-11 17:33:58 +02:00
f7993eaf2b remove ACP_SHARED Declaration 2022-08-11 17:17:52 +02:00
d27c52b526 Split Job and Scope Aggregators 2022-08-11 17:13:24 +02:00
312a23e91f split ReferrerAggregator 2022-08-11 16:57:44 +02:00
93eb9220a7 split ReferrerFilter, logiquement ce sont des filtres différents 2022-08-11 16:41:15 +02:00
b614149fab translations chain 2022-08-11 16:39:54 +02:00
c45ef7d74f exports: fix initiateQuery to always begin from acp entity 2022-08-11 15:05:08 +02:00
12cae472d6 Merge branch '111_exports' into social_action_exports 2022-08-11 14:08:33 +02:00
b8d187c82b exports evaluation: fix root query from acp 2022-08-11 14:04:15 +02:00
6e467a62d4 exports: simplify with querybuilder method to getRootAlias 2022-08-11 12:04:41 +02:00
70488a935d exports: test if a join is already loaded
when a specific join has to be shared between combined filters/aggregators, we need to check if it has been already loaded.
we cannot load it into indicator because result is wrong
2022-08-11 12:01:54 +02:00
18c17818f6 exports: add ChildrenNumber Aggregator 2022-08-10 17:17:29 +02:00
bc5d610f80 exports: add on_date parameter on Composition Filter/Aggregator 2022-08-10 16:50:59 +02:00
c693dfde66 a same join() in filter and aggregator make error if we combine them 2022-08-10 16:33:05 +02:00
3463ff8e2e exports: add Composition Filter and Aggregator in Household exports 2022-08-10 16:25:38 +02:00
3f4d4497af exports: add new countHousehold export 2022-08-10 14:35:38 +02:00
0c2c364eb6 trying to get dynamic form to work 2022-08-10 13:58:51 +02:00
8efbf02f64 exports: add new MaxDate Filter for evaluation 2022-08-10 10:57:32 +02:00
208d625258 merge conflict resolved 2022-08-10 10:37:19 +02:00
6eddb420cc fix nationality aggregator 2022-08-10 10:36:32 +02:00
3c451209c7 fix nationality aggregator 2022-08-10 10:35:32 +02:00
955d4a9e7a exports: add new EvaluationType Filter and Aggregator 2022-08-10 10:21:05 +02:00
20949fdb5c Merge branch '111_exports' into social_action_exports 2022-08-10 10:05:26 +02:00
2c7a128348 vendee filters and aggregators moved to root folder 2022-08-09 16:49:38 +02:00
97ec921a0a small adjustments 2022-08-09 16:15:27 +02:00
5b4b7473c5 residential address at user filter done 2022-08-09 16:14:41 +02:00
6010559936 translations added 2022-08-09 16:14:17 +02:00
a3cae28613 residential address at thirdparty filter : not done! 2022-08-09 16:14:01 +02:00
9a2af662c0 use translatablestringhelper 2022-08-09 16:13:24 +02:00
93d0fbead5 exports: create new indicator to count evaluations 2022-08-09 16:06:32 +02:00
72a5a6cd27 hop 2022-08-09 15:22:05 +02:00
b8bb0b1209 exports: improve translations 2022-08-09 15:11:01 +02:00
ed1dde4713 exports: share referrer aggregator 2022-08-09 11:30:42 +02:00
f817ca9671 exports: add a new intensity aggregator 2022-08-09 11:11:53 +02:00
5940e2c0b7 exports: add new Confidential and Emergency Aggregators 2022-08-08 17:39:53 +02:00
ace2cb6151 exports: add new AdministrativeLocation aggregator 2022-08-08 16:59:03 +02:00
ec7325ebbd exports: add a new ClosingMotive aggregator 2022-08-08 16:36:01 +02:00
643f37509f exports: share Scope and Job Aggregators 2022-08-08 16:12:34 +02:00
fcd7ae3b8d exports: add new aggregators for acp 2022-08-08 15:22:56 +02:00
f37e7cf393 moving files into separate folders for better overview 2022-08-04 14:49:48 +02:00
faef81a90b moving files into separate folders for better overview 2022-08-04 14:25:23 +02:00
95006b9643 exports: use Declarations for each applyOn 2022-08-04 14:12:25 +02:00
2f8bafa2e2 exports: split yaml services accompanying_period and accompanying_course
* the 3 old filters are applyOn person -> move it in PersonFilters
* there is a conditionnal option to enable them -> split yaml files
2022-08-04 12:46:06 +02:00
25e008f3e2 Merge branch '111_exports' into social_action_exports 2022-08-04 12:17:59 +02:00
43d45a5d04 tests: fix missing use statement 2022-08-04 11:30:52 +02:00
ea1a53ed37 tests: add missing FilterTest 2022-08-04 11:30:41 +02:00
28599adf48 tests on new ActiveOnDate and ActiveOneDayBetweenDates filters 2022-08-04 09:07:45 +02:00
e3743d3593 tests new filters ReferrerFilter and OpenBetweenDatesFilter (wip.. test failed) 2022-08-04 09:07:45 +02:00
2413c986ed exports: add a new GeographicUnitStat Filter (wip) 2022-08-04 09:07:45 +02:00
6921e4a40d exports: add new RequestorFilter 2022-08-03 20:13:29 +02:00
ac0c221267 start activity exports adaptations and new additions 2022-08-03 16:48:33 +02:00
32bb868b2b start with person aggregators 2022-08-03 16:21:57 +02:00
2ff346d7d8 Merge branch '111_exports' into social_action_exports 2022-08-03 15:22:32 +02:00
9e2ef9eae6 fix redundant referrerFilter 2022-08-03 15:18:22 +02:00
cfd590442f moving files into separate folders for better overview 2022-08-03 15:15:52 +02:00
cf7cf664a9 person filters added 2022-08-03 14:35:55 +02:00
ebb6ee4a41 agefilter added for persons 2022-08-02 15:07:56 +02:00
c72dc83c06 adjust countperson to make it work again 2022-08-02 13:44:28 +02:00
ea12c60154 more aggregator tests 2022-08-02 12:31:29 +02:00
40d92d11fc renaming aggregator job and scope for generic use 2022-08-02 11:30:30 +02:00
28dc99ff3f tests added for filters and some aggregators 2022-08-02 11:24:08 +02:00
20c1a287d8 merge with 111_export 2022-08-02 11:23:28 +02:00
6e439adce2 export new administrative location filter 2022-08-01 16:50:02 +02:00
4794e5e7b5 export new ActivityTypeFilter 2022-08-01 15:45:31 +02:00
bc2209319a export new EvaluationFilter 2022-08-01 14:32:11 +02:00
b511517a0f export new SocialActionFilter 2022-08-01 12:45:04 +02:00
758c56482b exports: add new ActiveOnDate filter 2022-08-01 11:30:32 +02:00
c401e34d63 exports: add new ActiveOneDayBetweenDates filter 2022-08-01 11:16:17 +02:00
7677b8aaa0 exports: add new OpenBetweenDates filter 2022-07-31 21:01:12 +02:00
dd06a262a2 remove commented unused 2022-07-31 19:22:39 +02:00
c1f578a811 exports: add new referrer filter, with mechanism to use it in acp or acpw context 2022-07-29 12:13:32 +02:00
635fe819a6 rename more generic for use in other exports 2022-07-28 15:57:02 +02:00
d59a22597d Merge branch '111_exports' into social_action_exports 2022-07-28 15:47:59 +02:00
8fdb2747ac Merge branch 'master' into social_action_exports 2022-07-28 15:45:46 +02:00
e6b66216ae actiontype, goal, and result aggregators created 2022-07-28 15:44:17 +02:00
d6b3ba48c0 fix the possibility to filter with multiple jobs or scopes 2022-07-28 12:05:42 +02:00
13b15f5057 renaming of class to avoid confusion 2022-07-28 11:24:36 +02:00
5c7513acd7 rename translations chains 2022-07-28 10:53:54 +02:00
8085fe2c17 referrer scope aggregator for social work actions 2022-07-27 21:25:13 +02:00
562a5678ef referrer job aggregator for social work actions 2022-07-27 21:19:13 +02:00
30501a68e3 referrer aggregator 2022-07-27 21:07:05 +02:00
b9186ed6e0 tests: write a simple Export test for new indicators + fix depreciation with BrowserKit client
cfr: https://symfony.com/doc/4.4/components/browser_kit.html#creating-a-client
2022-07-27 18:39:58 +02:00
5c82ccc49d user scope and job filter on socialwork actions 2022-07-27 17:08:50 +02:00
455a50d292 Merge branch 'social_action_exports' of gitlab.com:Chill-Projet/chill-bundles into social_action_exports 2022-07-27 16:05:04 +02:00
a40e2de91d rename filters to avoid confusion 2022-07-27 16:04:40 +02:00
357c7db5f1 rename filters to avoid confusion 2022-07-27 16:02:11 +02:00
ab2b2bc235 some advancements on dynamic socialaction filter 2022-07-27 15:50:56 +02:00
287d8f546b add simple test file for each new filter 2022-07-27 14:26:14 +02:00
8bbca12487 fix ProphecyTrait in AbstractFilter to use it with each filter 2022-07-27 14:23:21 +02:00
3dabd031f4 fix tests failed in exports 2022-07-27 14:23:21 +02:00
232cf96cb2 tests: write a simple Filter test for EmergencyFilter 2022-07-27 14:23:21 +02:00
cb42b68c33 filter for agents traitants 2022-07-26 15:46:16 +02:00
d5d38053cd renaming of export to avoid confusion + start of agent traitant filter 2022-07-26 14:43:21 +02:00
b12e5e78b6 reorganize into separate config file 2022-07-26 13:18:15 +02:00
a15c88dd35 merge 111 branch into this one 2022-07-26 11:57:54 +02:00
5f329101e1 title changed back to longer version 2022-07-26 11:44:20 +02:00
3a73af4fd0 Merge branch 'social_action_exports' of gitlab.com:Chill-Projet/chill-bundles into social_action_exports 2022-07-26 11:42:29 +02:00
9798cb8a09 due to excel limit, title can not exceed 31 characters 2022-07-26 11:41:16 +02:00
5ba8c4babc fixes to make count social actions export work 2022-07-26 11:22:17 +02:00
a06a2c9592 start of social actions export 2022-07-26 10:41:24 +02:00
ef827a1322 revert stepFilter to uniq selection
(cfr ddac410b2e)
2022-07-25 18:24:24 +02:00
e728c00671 exports: create intensity filter 2022-07-25 16:51:58 +02:00
da224ea4bc exports: create emergency filter 2022-07-25 16:35:36 +02:00
ddac410b2e exports: fix choice forms builder with multiple selections 2022-07-25 16:20:14 +02:00
43bedc41a7 exports: create confidential filter 2022-07-25 15:42:51 +02:00
ef5b3b24e4 exports: create closingmotive filter 2022-07-25 14:47:08 +02:00
057617c1cb separate accompanying_period exports in yaml service definition 2022-07-25 14:04:23 +02:00
e56520f4b1 exports: create origin filter 2022-07-25 12:36:22 +02:00
04ca61be81 rename data attribute 2022-07-25 12:35:35 +02:00
bf0ca7b777 exports: fix logic in userScope and userJob filter 2022-07-25 11:51:23 +02:00
fdd83de233 Merge branch '605_load-bootstrap' into 'master'
issue 605: improve the way chill loads bootstrap module

See merge request Chill-Projet/chill-bundles!446
2022-07-25 08:27:16 +00:00
97e8b3c9c1 fix yarn/webpack error when compiling
(correction on commit 340310be62)
2022-07-25 09:56:15 +02:00
5744a68f5f issue 605: remove old scratch sass framework 2022-07-22 14:13:16 +02:00
ddd0aeb7b4 issue 605: improve the way chill loads bootstrap module
custom variables are loaded before bootstrap variables, custom maps are loaded after
2022-07-22 14:08:43 +02:00
d5d3866122 fix conflict with where clause in indicator ; initiate a filter test 2022-07-20 23:17:05 +02:00
0d38d4df40 social issues filter: improve render with parents, add parents in where clause 2022-07-20 19:33:42 +02:00
bb22317eb1 exports: add new social issues filter (wip) 2022-07-20 16:38:46 +02:00
b608976326 exports: add new step filter 2022-07-20 13:47:15 +02:00
7f1dadc136 user scope/job filters: improve translation in csv 2022-07-20 11:24:13 +02:00
ff8a32a321 user scope filter: change query (TO CHECK) 2022-07-20 11:23:03 +02:00
d7c1498882 english corrected 2022-07-19 14:20:38 +00:00
70f118011b exports: add a new user job filter 2022-07-19 15:04:57 +02:00
da556248d4 Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2022-07-19 13:05:58 +02:00
881e608c87 exports: add a new user scope filter 2022-07-18 18:45:02 +02:00
80df7b2f72 update changelog 2022-07-18 16:37:34 +02:00
e6caeecc50 add composition type to admin menu 2022-07-18 16:36:13 +02:00
juminet
8651d3232a Merge branch 'issue612_add_field_accperiodresource_doc_gen' into 'master'
Issue612 add field accperiodresource doc gen

See merge request Chill-Projet/chill-bundles!445
2022-07-18 13:43:43 +00:00
nobohan
d55ba36619 [person] add comment field to accompanying period resource docgen 2022-07-18 15:39:00 +02:00
34923df43c exports avg_accompanying_course_duration with closingdate parameter 2022-07-18 14:38:48 +02:00
903ac2ff69 exports: init sum_accompanying_course_duration (replace list_accompanying_course) 2022-07-14 14:42:53 +02:00
3d0dd46ea5 fix count_accompanying_course query: no centers, not count DRAFT courses 2022-07-14 14:27:55 +02:00
e323937405 Temporarily disable ACL on new exports 2022-07-14 10:26:26 +02:00
2f42b9411a which acl is CONFIDENTIAL_CRUD ? 2022-07-13 14:19:12 +02:00
fe595860f2 replace 'hideLabel' form option by 'fullWidth'
sometimes we want fullWidth and display label
2022-07-13 13:36:20 +02:00
bda203d11d add accompanying courses exports in a group 2022-07-13 10:41:30 +02:00
3cdcccc037 add activity exports in a group 2022-07-13 10:32:27 +02:00
2ce145cace wip.. setting acl for new accompanying course exports 2022-07-13 10:14:54 +02:00
ec38dc4d21 add person exports in a group 2022-07-13 10:14:54 +02:00
28ed09b9d9 exports: add 2 new exports for accompanying courses 2022-07-12 15:18:37 +02:00
56bed12886 exports: fix errors and depreciations 2022-07-12 14:19:50 +02:00
e9fca1288a exports: fix errors and depreciations 2022-07-12 13:07:48 +02:00
9fa3d596bb split title and description for list activity export 2022-07-12 13:06:29 +02:00
ef9a155cc1 bootstrap styles in export section 2022-07-11 11:09:46 +02:00
340310be62 bootstrap styles in export section 2022-07-07 16:08:34 +02:00
7b64269bb5 fix show/hide in exports filter + better styles 2022-07-07 15:20:50 +02:00
989c4e90e5 enable show_hide on export pages 2022-07-05 16:39:48 +02:00
fcac977d13 existing exports, better translations 2022-07-05 15:46:43 +02:00
2b5accaeac override export activity translations 2022-07-05 13:42:32 +02:00
376 changed files with 15090 additions and 10222 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.composer/*
composer
composer.phar
composer.lock
docs/build/

View File

@@ -11,14 +11,24 @@ and this project adheres to
## Unreleased
<!-- write down unreleased development here -->
## Test releases
### 2.0.0-beta2
* [workflow]: Fixed: the notification is sent when the user is added to the first step.
* [budget] Feature: allow to desactivate some charges and resources, adding an `active` key in the configuration
* [person] Feature: on Evaluation, allow to configure an URL from the admin
### 2022-06
* [workflow]: added pagination to workflow list page
* [homepage_widget]: null error on tasks widget fixed
* [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty
* [search]: Order of birthdate fields changed in advanced search to avoid confusion.
* [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675)
* [household]: Reposition and cut button for enfant hors menage have been deleted (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/620)
## Test releases
* [admin]: Add crud for composition type in admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/611)
### 2022-05-30

BIN
composer

Binary file not shown.

View File

@@ -10,17 +10,17 @@
Exports
*******
Export is an important issue for the Chill software : users should be able to :
Export is an important issue within the Chill software : users should be able to :
- compute statistics about their activity ;
- list "things" which make part of their activities.
- list "things" which are a part of their activities.
The `main bundle`_ provides a powerful framework to build custom queries with re-usable parts across differents bundles.
.. contents:: Table of content
:local:
.. seealso::
.. seealso::
`The issue where this framework was discussed <https://git.framasoft.org/Chill-project/Chill-Main/issues/9>`_
Provides some information about the pursued features and architecture.
@@ -32,37 +32,37 @@ Concepts
Some vocabulary: 3 "Export elements"
------------------------------------
Four terms are used for this framework :
Four terms are used for this framework :
exports
provides some basic operation on the date. Two kind of exports are available :
Exports
provide some basic operation on the data. Two kinds of exports are available :
- computed data : it may be "the number of people", "the number of activities", "the duration of activities", ...
- list data : it may be "the list of people", "the list of activity", ...
- list data : it may be "the list of people", "the list of activities", ...
filters
The filters make a filter on the date: it removes some information the user doesn't want to introduce in the computation done by export. In other word, filters make a filter...
Filters
The filters create a filter on the data: it removes some information the user doesn't want to introduce in the computation done by the export.
Example of filter: "people under 18 years olds", "activities between the 1st of June and the 31st December", ...
Example of a filter: "people under 18 years olds", "activities between the 1st of June and the 31st December", ...
aggregators
The aggregator aggregates the data into some group (some software use the term 'bucket').
Aggregators
The aggregator aggregates the data into some group (some software use the term 'bucket').
Example of aggregator : "group people by gender", "group people by nationality", "group activity by type", ...
Example of an aggregator : "group people by gender", "group people by nationality", "group activity by type", ...
formatters
The formatters format the data into a :class:`Symfony\Component\HttpFoundation\Response`, which will be returned "as is" by the controller to the web client.
Formatters
The formatters format the data into a :class:`Symfony\Component\HttpFoundation\Response`, which will be returned "as is" by the controller to the web client.
Example of formatter: "format data as CSV", "format data as ods spreadsheet", ...
Example of a formatter: "format data as CSV", "format data as an ods spreadsheet", ...
Anatomy of an export
---------------------
An export may be explained as a sentence, where each part of this sentence refers to one or multiple exports element. Examples :
An export can be thought of as a sentence where each part of this sentence refers to one or multiple export elements. Examples :
**Example 1**: Count the number of people having at least one activity in the last 12 month, and group them by nationality and gender, and format them in a CSV spreadsheet.
Here :
Here :
- *count the number of people* is the export part
- *having at least one activity* is the filter part
@@ -72,10 +72,10 @@ Here :
Note that :
- aggregators, filters, exports and aggregators are cross-bundle. Here the bundle *activity* provides a filter which apply on an export provided by the person bundle ;
- there may exists multiple aggregator or filter for one export. Currently, only one export is allowed.
- Aggregators, filters, exports and formatters are cross-bundle. Here the bundle *activity* provides a filter which is applied on an export provided by the person bundle ;
- Multiple aggregator or filter for one export may exist. Currently, only one export is allowed.
The result might be :
The result might be :
+-----------------------+----------------+---------------------------+
| Nationality | Gender | Number of people |
@@ -89,9 +89,9 @@ The result might be :
| France | Female | 150 |
+-----------------------+----------------+---------------------------+
**Example 2**: Count the average duration of an activity with type "meeting", which occurs between the 1st of June and the 31st of December, group them by week, and format the data in a OpenDocument spreadsheet.
**Example 2**: Count the average duration of an activity with type "meeting", which occurs between the 1st of June and the 31st of December, group them by week, and format the data in an OpenDocument spreadsheet.
Here :
Here :
- *count the average duration of an activity* is the export part
- *activity with type meeting* is a filter part
@@ -102,7 +102,7 @@ Here :
The result might be :
+-----------------------+----------------------+
| Week | Number of activities |
| Week | Number of activities |
+=======================+======================+
| 2015-10 | 10 |
+-----------------------+----------------------+
@@ -116,77 +116,77 @@ The result might be :
Authorization and exports
-------------------------
Exports, filters and aggregators should not make see data the user is not allowed to see.
Exports, filters and aggregators should not show data the user is not allowed to see within the application.
In other words, developers are required to take care of user authorization for each export.
It should exists a special role that should be granted to users which are allowed to build exports. For more simplicity, this role should apply on center, and should not requires special circles.
There should be a specific role that grants permission to users who are allowed to build exports. For more simplicity, this role should apply on a center, and should not require special circles.
How does the magic works ?
How does the magic work ?
===========================
To build an export, we rely on the capacity of the database to execute queries with aggregate (i.e. GROUP BY) and filter (i.e. WHERE) instructions.
An export is an SQL query which is initiated by an export, and modified by aggregators and filters.
.. note::
.. note::
**Example**: Count the number of people having at least one activity in the last 12 month, and group them by nationality and gender
1. The report initiate the query
1. The report initiates the query
.. code-block:: SQL
SELECT count(people.*) FROM people
2. The filter add a where and join clause :
2. The filter adds a where and join clause :
.. code-block:: SQL
SELECT count(people.*) FROM people
RIGHT JOIN activity
SELECT count(people.*) FROM people
RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
3. The aggregator "nationality" add a GROUP BY clause and a column in the SELECT statement:
3. The aggregator "nationality" adds a GROUP BY clause and a column in the SELECT statement:
.. code-block:: sql
SELECT people.nationality, count(people.*) FROM people
RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
SELECT people.nationality, count(people.*) FROM people
RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
GROUP BY nationality
4. The aggregator "gender" do the same job as the nationality aggregator : it adds a GROUP BY clause and a column in the SELECT statement :
4. The aggregator "gender" does the same job as the nationality aggregator : it adds a GROUP BY clause and a column in the SELECT statement :
.. code-block:: sql
SELECT people.nationality, people.gender, count(people.*)
FROM people RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
SELECT people.nationality, people.gender, count(people.*)
FROM people RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
GROUP BY nationality, gender
Each filter, aggregator and filter may collect parameters from the user by providing a form. This form is appended to the export form. Here is an example.
Each filter, aggregator and filter may collect parameters from the user through a form. This form is appended to the export form. Here is an example.
.. figure:: /_static/screenshots/development/export_form-fullpage.png
The screenshot show the export form for ``CountPeople`` (Nombre de personnes). The filter by date of birth is checked (*Filtrer par date de naissance de la personne*), which allow to show a subform, which is provided by the :class:`Chill\PersonBundle\Export\Filter\BirthdateFilter`. The other filter, which are unchecked, does not show the subform.
The screenshot shows the export form for ``CountPeople`` (Nombre de personnes). The filter by date of birth is checked (*Filtrer par date de naissance de la personne*), which triggers a subform, which is provided by the :class:`Chill\PersonBundle\Export\Filter\BirthdateFilter`. The other unchecked filter does not show the subform.
Two aggregators are also checked : by Country of birth (*Aggréger les personnes par pays de naissance*, corresponding class is :class:`Chill\PersonBundle\Export\Aggregator\CountryOfBirthAggregator`, which also open a subform. The aggregator by gender (*Aggréger les personnes par genre*) is also checked, but there is no corresponding subform.
Two aggregators are also checked : by Country of birth (*Aggréger les personnes par pays de naissance*, the corresponding class is :class:`Chill\PersonBundle\Export\Aggregator\CountryOfBirthAggregator`, which also triggers a subform. The aggregator by gender (*Aggréger les personnes par genre*) is also checked, but there is no corresponding subform.
The Export Manager
------------------
The Export manager (:class:`Chill\MainBundle\Export\ExportManager` is the central class which register all exports, aggregators, filters and formatters.
The Export manager (:class:`Chill\MainBundle\Export\ExportManager` is the central class which registers all exports, aggregators, filters and formatters.
The export manager is also responsible for orchestrating the whole export process, producing a :class:`Symfony\FrameworkBundle\HttpFoundation\Request` to each export request.
The export manager is also responsible for orchestrating the whole export process, producing a :class:`Symfony\FrameworkBundle\HttpFoundation\Request` for each export request.
The export form step
--------------------
The form step allow to build a form, aggregating different parts of the module.
The form step allows you to build a form, combining different parts of the module.
The building of forms is separated between different subform, which are responsible for rendering their part of the form (aggregators, filters, and export).
The building of forms is split into different subforms, where each one is responsible for rendering their part of the form (aggregators, filters, and export).
.. figure:: /_static/puml/exports/form_steps.png
:scale: 40%
@@ -194,12 +194,12 @@ The building of forms is separated between different subform, which are responsi
The formatter form step
-----------------------
The formatter form is processed *after* the user filled the export form. It is built the same way, but receive in parameters the data entered by the user on the previous step (i.e. export form). It may then adapt it accordingly (example: show a list of columns selected in aggregators).
The formatter form is processed *after* the user filled the export form. It is built the same way, but receives the data entered by the user on the previous step as parameters (i.e. export form). It may then adapt it accordingly (example: show a list of columns selected in aggregators).
Processing the export
---------------------
The export process may be explained by this schema :
The export process can be explained by this schema :
.. figure:: /_static/puml/exports/processing_export.png
:scale: 40%
@@ -219,20 +219,20 @@ This is an example of the ``CountPerson`` export :
:language: php
:linenos:
* **Line 36**: the ``getType`` function return a string. This string will be used to find the aggregtors and filters which will apply to this export.
* **Line 41**: a simple description to help user to understand what your export does.
* **Line 36**: the ``getType`` function returns a string. This string will be used to find the aggregtors and filters which will apply to this export.
* **Line 41**: a simple description to help users understand what your export does.
* **Line 46**: The title of the export. A summary of what your export does.
* **Line 51**: The list of roles requires to execute this export.
* **Line 51**: The list of roles required to execute this export.
* **Line 56**: We initiate the query here...
* **Line 59**: We have to filter the query with centers the users checked in the form. We process the $acl variable to get all ``Center`` object in one array
* **Line 63**: We create the query, with a query builder.
* **Line 74**: We simply returns the result, but take care of hydrating the results as an array.
* **Line 103**: return the list of formatters types which are allowed to apply on this filter
* **Line 59**: We have to filter the query with centers the users checked in the form. We process the $acl variable to get all ``Center`` objects in one array
* **Line 63**: We create the query with a query builder.
* **Line 74**: We return the result, but make sure to hydrate the results as an array.
* **Line 103**: return the list of formatter types which are allowed to be applied on this filter
Filters
-------
This is an example of the *filter by birthdate*. This filter ask some information in a form (`buildForm` is not empty), and this form must be validated. To performs this validations, we implement a new Interface: :class:`Chill\MainBundle\Export\ExportElementValidatedInterface`:
This is an example of the *filter by birthdate*. This filter asks some information through a form (`buildForm` is not empty), and this form must be validated. To perform this validation, we implement a new Interface: :class:`Chill\MainBundle\Export\ExportElementValidatedInterface`:
.. literalinclude:: /_static/code/exports/BirthdateFilter.php
:language: php

View File

@@ -104,6 +104,29 @@ The password is always ``password``.
Now, read `Operations` below.
Prepare for development
***********************
Add a Gitlab token to ensure that you get always the source code:
1. generate a gitlab token there: https://gitlab.com/oauth/token
2. run this command (in php container, at the app's root): :code:`composer config gitlab-token.gitlab.com <your token>`
The auth token should appears now in the directory :code:`.composer`:
.. code-block: bash
$ cat .composer/auth.json
{
"gitlab-token": {
"gitlab.com": "<your token>"
}
}
See also "how to switch branch and get new dependencies".
Operations
**********
@@ -211,6 +234,25 @@ How to run webpack interactively
Executing :code:`bash docker-node.sh` will open a terminal in a node container, with volumes mounted.
How to switch the branch for chill-bundles, and get new dependencies
====================================================================
During development, you will switch to new branches for chill-bundles. As long as the dependencies are equals, this does not cause any problem. But sometimes, a new branch introduces a new dependency, and you must download it.
In order to do that without pain, use those steps:
0. Ensuire you have a token, set
1. at the app's root, update the `composer.json` to your current branch:
.. code-block:: json
{
"require": {
"chill-bundles": "dev-<my-branch>@dev"
}
2. mount into the php container, and run `composer update`
Build the documentation API
===========================

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class BySocialActionAggregator implements AggregatorInterface
{
private SocialActionRender $actionRender;
private SocialActionRepository $actionRepository;
public function __construct(
SocialActionRender $actionRender,
SocialActionRepository $actionRepository
) {
$this->actionRender = $actionRender;
$this->actionRepository = $actionRepository;
}
public function getLabels($key, array $values, $data)
{
return function($value) {
if ('_header' === $value) {
return 'Social action';
}
$sa = $this->actionRepository->find($value);
return $this->actionRender->renderString($sa, []);
};
}
public function getQueryKeys($data): array
{
return ['socialaction_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group activity by linked socialaction';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if(!in_array('socialaction', $qb->getAllAliases())) {
$qb->join('activity.socialActions', 'socialaction');
}
$qb->addSelect('socialaction.id AS socialaction_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('socialaction_aggregator');
} else {
$qb->groupBy('socialaction_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class BySocialIssueAggregator implements AggregatorInterface
{
private SocialIssueRepository $issueRepository;
private SocialIssueRender $issueRender;
public function __construct(
SocialIssueRepository $issueRepository,
SocialIssueRender $issueRender
) {
$this->issueRepository = $issueRepository;
$this->issueRender = $issueRender;
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ($value === '_header') {
return 'Social issues';
}
$i = $this->issueRepository->find($value);
return $this->issueRender->renderString($i, []);
};
}
public function getQueryKeys($data): array
{
return ['socialissue_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group activity by linked socialissue';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('socialissue', $qb->getAllAliases())) {
$qb->join('activity.socialIssues', 'socialissue');
}
$qb->addSelect('socialissue.id AS socialissue_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('socialissue_aggregator');
} else {
$qb->groupBy('socialissue_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class ByThirdpartyAggregator implements AggregatorInterface
{
private ThirdPartyRepository $thirdPartyRepository;
private ThirdPartyRender $thirdPartyRender;
public function __construct(
ThirdPartyRepository $thirdPartyRepository,
ThirdPartyRender $thirdPartyRender
) {
$this->thirdPartyRepository = $thirdPartyRepository;
$this->thirdPartyRender = $thirdPartyRender;
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ($value === '_header') {
return 'Accepted thirdparty';
}
$tp = $this->thirdPartyRepository->find($value);
return $this->thirdPartyRender->renderString($tp, []);
};
}
public function getQueryKeys($data): array
{
return ['thirdparty_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group activity by linked thirdparties';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('thirdparty', $qb->getAllAliases())) {
$qb->join('activity.thirdParties', 'thirdparty');
}
$qb->addSelect('thirdparty.id AS thirdparty_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('thirdparty_aggregator');
} else {
$qb->groupBy('thirdparty_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class ByUserAggregator implements AggregatorInterface
{
private UserRepository $userRepository;
private UserRender $userRender;
public function __construct(
UserRepository $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ($value === '_header') {
return 'Accepted users';
}
$u = $this->userRepository->find($value);
return $this->userRender->renderString($u, []);
};
}
public function getQueryKeys($data): array
{
return ['users_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group activity by linked users';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('user', $qb->getAllAliases())) {
$qb->join('activity.users', 'user');
}
$qb->addSelect('user.id AS users_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('users_aggregator');
} else {
$qb->groupBy('users_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Closure;
use Doctrine\ORM\QueryBuilder;
use RuntimeException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class DateAggregator implements AggregatorInterface
{
private const CHOICES = [
'by month' => 'month',
'by week' => 'week',
'by year' => 'year',
];
private CONST DEFAULT_CHOICE = 'year';
private TranslatorInterface $translator;
public function __construct(
TranslatorInterface $translator
) {
$this->translator = $translator;
}
public function getLabels($key, array $values, $data)
{
return function ($value) use ($data): string {
if ($value === '_header') {
return 'by '. $data['frequency'];
}
switch ($data['frequency']) {
case 'month':
$month = \DateTime::createFromFormat('!m', $value);
return sprintf(
"%02d (%s)",
$value,
$month->format('M')
);
case 'week':
//return $this->translator->trans('for week') .' '. $value ;
case 'year':
//return $this->translator->trans('in year') .' '. $value ;
default:
return $value;
}
};
}
public function getQueryKeys($data): array
{
return ['date_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('frequency', ChoiceType::class, [
'choices' => self::CHOICES,
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function getTitle(): string
{
return 'Group activity by date';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$order = null;
switch ($data['frequency']) {
case 'month':
$fmt = 'MM'; break;
case 'week':
$fmt = 'IW'; break;
case 'year':
$fmt = 'YYYY'; $order = 'DESC'; break;
default:
throw new RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency']));
}
$qb->addSelect(sprintf("TO_CHAR(activity.date, '%s') AS date_aggregator", $fmt));
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('date_aggregator');
} else {
$qb->groupBy('date_aggregator');
}
$orderBy = $qb->getDQLPart('orderBy');
if (!empty($orderBy)) {
$qb->addOrderBy('date_aggregator', $order);
} else {
$qb->orderBy('date_aggregator', $order);
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\LocationTypeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class LocationTypeAggregator implements AggregatorInterface
{
private LocationTypeRepository $locationTypeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
LocationTypeRepository $locationTypeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->locationTypeRepository = $locationTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ($value === '_header') {
return 'Accepted locationtype';
}
$lt = $this->locationTypeRepository->find($value);
return $this->translatableStringHelper->localize(
$lt->getTitle()
);
};
}
public function getQueryKeys($data): array
{
return ['locationtype_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group activity by locationtype';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('location', $qb->getAllAliases())) {
$qb->join('activity.location', 'location');
}
$qb->addSelect('IDENTITY(location.locationType) AS locationtype_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('locationtype_aggregator');
} else {
$qb->groupBy('locationtype_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class UserScopeAggregator implements AggregatorInterface
{
private ScopeRepository $scopeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
ScopeRepository $scopeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ($value === '_header') {
return 'Scope';
}
$s = $this->scopeRepository->find($value);
return $this->translatableStringHelper->localize(
$s->getName()
);
};
}
public function getQueryKeys($data): array
{
return ['userscope_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group activity by userscope';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('user', $qb->getAllAliases())) {
$qb->join('activity.user', 'user');
}
$qb->addSelect('IDENTITY(user.mainScope) AS userscope_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('userscope_aggregator');
} else {
$qb->groupBy('userscope_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -44,16 +45,24 @@ class ActivityTypeAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
// add select element
$qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY));
if (!in_array('type', $qb->getAllAliases())) {
$qb->join('activity.activityType', 'type');
}
// add the "group by" part
$qb->addGroupBy(self::KEY);
$qb->addSelect(sprintf('IDENTITY(activity.activityType) AS %s', self::KEY));
$groupby = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy(self::KEY);
} else {
$qb->groupBy(self::KEY);
}
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -11,9 +11,11 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
@@ -25,10 +27,14 @@ class ActivityUserAggregator implements AggregatorInterface
private UserRepository $userRepository;
private UserRender $userRender;
public function __construct(
UserRepository $userRepository
UserRepository $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
}
public function addRole()
@@ -47,7 +53,7 @@ class ActivityUserAggregator implements AggregatorInterface
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@@ -62,10 +68,12 @@ class ActivityUserAggregator implements AggregatorInterface
return function ($value) {
if ('_header' === $value) {
return 'activity user';
return 'Activity user';
}
return $this->userRepository->find($value)->getUsername();
$u = $this->userRepository->find($value);
return $this->userRender->renderString($u, []);
};
}

View File

@@ -9,8 +9,9 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
namespace Chill\ActivityBundle\Export\Aggregator\PersonAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityReasonCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
@@ -104,9 +105,9 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
}
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY_PERSON;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -0,0 +1,24 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export;
/**
* This class declare constants used for the export framework.
*/
abstract class Declarations
{
public const ACTIVITY = 'activity';
public const ACTIVITY_ACP = "activity_linked_to_acp";
public const ACTIVITY_PERSON = "activity_linked_to_person";
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Closure;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class AvgActivityDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getTitle(): string
{
return 'Average activity linked to an accompanying period duration';
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Average activities linked to an accompanying period duration by various parameters.';
}
public function getLabels($key, array $values, $data)
{
if ('export_avg_activity_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period duration' : $value;
}
public function getQueryKeys($data): array
{
return ['export_avg_activity_duration'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp')
;
$qb->select('AVG(activity.durationTime) as export_avg_activity_duration');
return $qb;
}
public function requiredRole(): Role
{
return new Role(ActivityStatsVoter::STATS);
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Closure;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getTitle(): string
{
return 'Average activity linked to an accompanying period visit duration';
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Average activities linked to an accompanying period visit duration by various parameters.';
}
public function getLabels($key, array $values, $data)
{
if ('export_avg_activity_visit_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period visit duration' : $value;
}
public function getQueryKeys($data): array
{
return ['export_avg_activity_visit_duration'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp')
;
$qb->select('AVG(activity.travelTime) as export_avg_activity_visit_duration');
return $qb;
}
public function requiredRole(): Role
{
return new Role(ActivityStatsVoter::STATS);
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class CountActivity implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Count activities linked to an accompanying period by various parameters.';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Number of activities linked to an accompanying period' : $value;
}
public function getQueryKeys($data): array
{
return ['export_count_activity'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Count activities linked to an accompanying period';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp')
;
$qb->select('COUNT(activity.id) as export_count_activity');
return $qb;
}
public function requiredRole(): Role
{
return new Role(ActivityStatsVoter::STATS);
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Closure;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class SumActivityDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getTitle(): string
{
return 'Sum activity linked to an accompanying period duration';
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Sum activities linked to an accompanying period duration by various parameters.';
}
public function getLabels($key, array $values, $data)
{
if ('export_sum_activity_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period duration' : $value;
}
public function getQueryKeys($data): array
{
return ['export_sum_activity_duration'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp')
;
$qb->select('SUM(activity.durationTime) as export_sum_activity_duration');
return $qb;
}
public function requiredRole(): Role
{
return new Role(ActivityStatsVoter::STATS);
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Closure;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class SumActivityVisitDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getTitle(): string
{
return 'Sum activity linked to an accompanying period visit duration';
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Sum activities linked to an accompanying period visit duration by various parameters.';
}
public function getLabels($key, array $values, $data)
{
if ('export_sum_activity_visit_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period visit duration' : $value;
}
public function getQueryKeys($data): array
{
return ['export_sum_activity_visit_duration'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp')
;
$qb->select('SUM(activity.travelTime) as export_sum_activity_visit_duration');
return $qb;
}
public function requiredRole(): Role
{
return new Role(ActivityStatsVoter::STATS);
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
}

View File

@@ -9,18 +9,21 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
namespace Chill\ActivityBundle\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\ActivityBundle\Export\Declarations;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class CountActivity implements ExportInterface
class CountActivity implements ExportInterface, GroupedExportInterface
{
protected ActivityRepository $activityRepository;
@@ -41,7 +44,7 @@ class CountActivity implements ExportInterface
public function getDescription()
{
return 'Count activities by various parameters.';
return 'Count activities linked to a person by various parameters.';
}
public function getLabels($key, array $values, $data)
@@ -50,7 +53,7 @@ class CountActivity implements ExportInterface
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Number of activities' : $value;
return static fn ($value) => '_header' === $value ? 'Number of activities linked to a person' : $value;
}
public function getQueryKeys($data)
@@ -65,27 +68,28 @@ class CountActivity implements ExportInterface
public function getTitle()
{
return 'Count activities';
return 'Count activities linked to a person';
}
public function getType()
public function getType(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this
->activityRepository
->createQueryBuilder('activity')
->select('COUNT(activity.id) as export_count_activity')
->join('activity.person', 'person');
$qb = $this->activityRepository->createQueryBuilder('activity')
->join('activity.person', 'person')
;
$qb->select('COUNT(activity.id) as export_count_activity');
$qb
->where($qb->expr()->in('person.center', ':centers'))
->setParameter('centers', $centers);
->setParameter('centers', $centers)
;
return $qb;
}
@@ -97,6 +101,15 @@ class CountActivity implements ExportInterface
public function supportsModifiers()
{
return ['person', 'activity'];
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of activities linked to a person';
}
}

View File

@@ -9,12 +9,13 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
namespace Chill\ActivityBundle\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use DateTime;
@@ -27,12 +28,14 @@ use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Chill\ActivityBundle\Export\Declarations;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use function array_key_exists;
use function count;
use function in_array;
class ListActivity implements ListInterface
class ListActivity implements ListInterface, GroupedExportInterface
{
protected EntityManagerInterface $entityManager;
@@ -94,7 +97,7 @@ class ListActivity implements ListInterface
public function getDescription()
{
return 'List activities';
return 'List activities linked to a person description';
}
public function getLabels($key, array $values, $data)
@@ -180,12 +183,12 @@ class ListActivity implements ListInterface
public function getTitle()
{
return 'List activities';
return 'List activity linked to a person';
}
public function getType()
public function getType(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
@@ -274,6 +277,15 @@ class ListActivity implements ListInterface
public function supportsModifiers()
{
return ['activity', 'person'];
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of activities linked to a person';
}
}

View File

@@ -9,12 +9,16 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
namespace Chill\ActivityBundle\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Chill\ActivityBundle\Export\Declarations;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
@@ -25,7 +29,7 @@ use Symfony\Component\Security\Core\Role\Role;
*
* The desired stat must be given in constructor.
*/
class StatActivityDuration implements ExportInterface
class StatActivityDuration implements ExportInterface, GroupedExportInterface
{
public const SUM = 'sum';
@@ -59,7 +63,7 @@ class StatActivityDuration implements ExportInterface
public function getDescription()
{
if (self::SUM === $this->action) {
return 'Sum activities duration by various parameters.';
return 'Sum activities linked to a person duration by various parameters.';
}
}
@@ -69,7 +73,7 @@ class StatActivityDuration implements ExportInterface
throw new LogicException(sprintf('The key %s is not used by this export', $key));
}
$header = self::SUM === $this->action ? 'Sum of activities duration' : false;
$header = self::SUM === $this->action ? 'Sum activities linked to a person duration' : false;
return static fn (string $value) => '_header' === $value ? $header : $value;
}
@@ -87,19 +91,19 @@ class StatActivityDuration implements ExportInterface
public function getTitle()
{
if (self::SUM === $this->action) {
return 'Sum activity duration';
return 'Sum activity linked to a person duration';
}
}
public function getType()
public function getType(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(
static fn (array $el): string => $el['center'],
static fn (array $el): Center => $el['center'],
$acl
);
@@ -125,6 +129,15 @@ class StatActivityDuration implements ExportInterface
public function supportsModifiers()
{
return ['person', 'activity'];
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of activities linked to a person';
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\ActivityBundle\Export\Declarations;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class BySocialActionFilter implements FilterInterface
{
private SocialActionRender $actionRender;
public function __construct(SocialActionRender $actionRender)
{
$this->actionRender = $actionRender;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_socialactions', EntityType::class, [
'class' => SocialAction::class,
'choice_label' => function (SocialAction $sa) {
return $this->actionRender->renderString($sa, []);
},
'multiple' => true,
'expanded' => true,
]);
}
public function getTitle(): string
{
return 'Filter activity by linked socialaction';
}
public function describeAction($data, $format = 'string'): array
{
$actions = [];
foreach ($data['accepted_socialactions'] as $sa) {
$actions[] = $this->actionRender->renderString($sa, []);
}
return ['Filtered activity by linked socialaction: only %actions%', [
'%actions%' => implode(", ou ", $actions)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
if (!in_array('socialaction', $qb->getAllAliases())) {
$qb->join('activity.socialActions', 'socialaction');
}
$clause = $qb->expr()->in('socialaction.id', ':socialactions');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('socialactions', $data['accepted_socialactions']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\ActivityBundle\Export\Declarations;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class BySocialIssueFilter implements FilterInterface
{
private SocialIssueRender $issueRender;
public function __construct(SocialIssueRender $issueRender)
{
$this->issueRender = $issueRender;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_socialissues', EntityType::class, [
'class' => SocialIssue::class,
'choice_label' => function(SocialIssue $si) {
return $this->issueRender->renderString($si, []);
},
'multiple' => true,
'expanded' => true,
]);
}
public function getTitle(): string
{
return 'Filter activity by linked socialissue';
}
public function describeAction($data, $format = 'string'): array
{
$issues = [];
foreach ($data['accepted_socialissues'] as $si) {
$issues[] = $this->issueRender->renderString($si, []);
}
return ['Filtered activity by linked socialissue: only %issues%', [
'%issues%' => implode(", ou ", $issues)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
if (!in_array('socialissue', $qb->getAllAliases())) {
$qb->join('activity.socialIssues', 'socialissue');
}
$clause = $qb->expr()->in('socialissue.id', ':socialissues');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('socialissues', $data['accepted_socialissues']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class ByUserFilter implements FilterInterface
{
private UserRender $userRender;
public function __construct(UserRender $userRender)
{
$this->userRender = $userRender;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_users', EntityType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true,
'expanded' => true,
]);
}
public function getTitle(): string
{
return 'Filter activity by linked users';
}
public function describeAction($data, $format = 'string'): array
{
$users = [];
foreach ($data['accepted_users'] as $u) {
$users[] = $this->userRender->renderString($u, []);
}
return ['Filtered activity by linked users: only %users%', [
'%users%' => implode(", ou ", $users)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
if (!in_array('user', $qb->getAllAliases())) {
$qb->join('activity.users', 'user');
}
$clause = $qb->expr()->in('user.id', ':users');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('users', $data['accepted_users']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\ActivityBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class EmergencyFilter implements FilterInterface
{
private const CHOICES = [
'activity is emergency' => true,
'activity is not emergency' => false,
];
private CONST DEFAULT_CHOICE = false;
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_emergency', ChoiceType::class, [
'choices' => self::CHOICES,
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function getTitle(): string
{
return 'Filter activity by emergency';
}
public function describeAction($data, $format = 'string'): array
{
foreach (self::CHOICES as $k => $v) {
if ($v === $data['accepted_emergency']) {
$choice = $k;
}
}
return ['Filtered activity by emergency: only %emergency%', [
'%emergency%' => $this->translator->trans($choice)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->eq('activity.emergency', ':emergency');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('emergency', $data['accepted_emergency']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Export\FilterInterface;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class LocationTypeFilter implements FilterInterface
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_locationtype', EntityType::class, [
'class' => LocationType::class,
'choice_label' => function(LocationType $type) {
return $this->translatableStringHelper->localize($type->getTitle());
},
'multiple' => true,
'expanded' => true,
]);
}
public function getTitle(): string
{
return 'Filter activity by locationtype';
}
public function describeAction($data, $format = 'string'): array
{
$types = [];
foreach ($data['accepted_locationtype'] as $type) {
$types[] = $this->translatableStringHelper->localize(
$type->getTitle()
);
}
return ['Filtered activity by locationtype: only %types%', [
'%types%' => implode(", ou ", $types)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('location', $qb->getAllAliases())) {
$qb->join('activity.location', 'location');
}
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('location.locationType', ':locationtype');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('locationtype', $data['accepted_locationtype']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\Export\FilterInterface;
use Chill\ActivityBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class SentReceivedFilter implements FilterInterface
{
private const CHOICES = [
'is sent' => Activity::SENTRECEIVED_SENT,
'is received' => Activity::SENTRECEIVED_RECEIVED,
];
private CONST DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT;
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_sentreceived', ChoiceType::class, [
'choices' => self::CHOICES,
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function getTitle(): string
{
return 'Filter activity by sentreceived';
}
public function describeAction($data, $format = 'string'): array
{
$sentreceived = array_flip(self::CHOICES)[$data['accepted_sentreceived']];
return ['Filtered activity by sentreceived: only %sentreceived%', [
'%sentreceived%' => $this->translator->trans($sentreceived)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->eq('activity.sentReceived', ':sentreceived');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('sentreceived', $data['accepted_sentreceived']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class UserFilter implements FilterInterface
{
private UserRender $userRender;
public function __construct(UserRender $userRender)
{
$this->userRender = $userRender;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_users', EntityType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true,
'expanded' => true,
'label' => 'Creators'
]);
}
public function getTitle(): string
{
return 'Filter activity by user';
}
public function describeAction($data, $format = 'string'): array
{
$users = [];
foreach ($data['accepted_users'] as $u) {
$users[] = $this->userRender->renderString($u, []);
}
return ['Filtered activity by user: only %users%', [
'%users%' => implode(", ou ", $users)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('activity.user', ':users');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('users', $data['accepted_users']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\FilterInterface;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class UserScopeFilter implements FilterInterface
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_userscope', EntityType::class, [
'class' => Scope::class,
'choice_label' => function (Scope $s) {
return $this->translatableStringHelper->localize(
$s->getName()
);
},
'multiple' => true,
'expanded' => true
]);
}
public function getTitle(): string
{
return 'Filter activity by userscope';
}
public function describeAction($data, $format = 'string'): array
{
$scopes = [];
foreach ($data['accepted_userscope'] as $s) {
$scopes[] = $this->translatableStringHelper->localize(
$s->getName()
);
}
return ['Filtered activity by userscope: only %scopes%', [
'%scopes%' => implode(", ou ", $scopes)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('user', $qb->getAllAliases())) {
$qb->join('activity.user', 'user');
}
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('user.mainScope', ':userscope');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('userscope', $data['accepted_userscope']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
}

View File

@@ -11,12 +11,13 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\Export\FilterType;
use DateTime;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
@@ -57,36 +58,23 @@ class ActivityDateFilter implements FilterInterface
$qb->setParameter('date_to', $data['date_to']);
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add(
'date_from',
DateType::class,
[
$builder
->add('date_from', ChillDateType::class, [
'label' => 'Activities after this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
]
);
$builder->add(
'date_to',
DateType::class,
[
])
->add('date_to', ChillDateType::class, [
'label' => 'Activities before this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
]
);
])
;
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var \Symfony\Component\Form\FormInterface $filterForm */

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
@@ -49,7 +50,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('activity.type', ':selected_activity_types');
$clause = $qb->expr()->in('activity.activityType', ':selected_activity_types');
if ($where instanceof Expr\Andx) {
$where->add($clause);
@@ -61,39 +62,33 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
$qb->setParameter('selected_activity_types', $data['types']);
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add(
'types',
EntityType::class,
[
'class' => ActivityType::class,
'choice_label' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getName()),
'multiple' => true,
'expanded' => false,
]
);
$builder->add('types', EntityType::class, [
'class' => ActivityType::class,
'choice_label' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getName()),
'group_by' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getCategory()->getName()),
'multiple' => true,
'expanded' => false,
]);
}
public function describeAction($data, $format = 'string')
{
// collect all the reasons'name used in this filter in one array
$reasonsNames = array_map(
fn (ActivityType $t): string => '"' . $this->translatableStringHelper->localize($t->getName()) . '"',
fn (ActivityType $t): string => $this->translatableStringHelper->localize($t->getName()),
$this->activityTypeRepository->findBy(['id' => $data['types']->toArray()])
);
return [
'Filtered by activity type: only %list%',
[
'%list%' => implode(', ', $reasonsNames),
],
];
return ['Filtered by activity type: only %list%', [
'%list%' => implode(', ou ', $reasonsNames),
]];
}
public function getTitle()

View File

@@ -9,9 +9,10 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
namespace Chill\ActivityBundle\Export\Filter\PersonFilters;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
@@ -79,9 +80,9 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
$qb->setParameter('selected_activity_reasons', $data['reasons']);
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY_PERSON;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
namespace Chill\ActivityBundle\Export\Filter\PersonFilters;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
@@ -102,7 +102,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
$qb->setParameter('person_having_activity_reasons', $data['reasons']);
}
public function applyOn()
public function applyOn(): string
{
return Declarations::PERSON_IMPLIED_IN;
}

View File

@@ -3,26 +3,48 @@ services:
autowire: true
autoconfigure: true
chill.activity.export.count_activity:
class: Chill\ActivityBundle\Export\Export\CountActivity
## Indicators
chill.activity.export.count_activity_linked_to_person:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\CountActivity
tags:
- { name: chill.export, alias: 'count_activity' }
- { name: chill.export, alias: 'count_activity_linked_to_person' }
chill.activity.export.sum_activity_duration:
class: Chill\ActivityBundle\Export\Export\StatActivityDuration
chill.activity.export.sum_activity_duration_linked_to_person:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\StatActivityDuration
tags:
- { name: chill.export, alias: 'sum_activity_duration' }
- { name: chill.export, alias: 'sum_activity_duration_linked_to_person' }
chill.activity.export.list_activity:
class: Chill\ActivityBundle\Export\Export\ListActivity
chill.activity.export.list_activity_linked_to_person:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\ListActivity
tags:
- { name: chill.export, alias: 'list_activity' }
- { name: chill.export, alias: 'list_activity_linked_to_person' }
chill.activity.export.reason_filter:
class: Chill\ActivityBundle\Export\Filter\ActivityReasonFilter
chill.activity.export.count_activity_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity
tags:
- { name: chill.export_filter, alias: 'activity_reason_filter' }
- { name: chill.export, alias: 'count_activity_linked_to_acp' }
chill.activity.export.sum_activity_duration_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityDuration
tags:
- { name: chill.export, alias: 'sum_activity_duration_linked_to_acp' }
chill.activity.export.sum_activity_visit_duration_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityVisitDuration
tags:
- { name: chill.export, alias: 'sum_activity_visit_duration_linked_to_acp' }
chill.activity.export.avg_activity_duration_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityDuration
tags:
- { name: chill.export, alias: 'avg_activity_duration_linked_to_acp' }
chill.activity.export.avg_activity_visit_duration_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityVisitDuration
tags:
- { name: chill.export, alias: 'avg_activity_visit_duration_linked_to_acp' }
## Filters
chill.activity.export.type_filter:
class: Chill\ActivityBundle\Export\Filter\ActivityTypeFilter
tags:
@@ -33,15 +55,61 @@ services:
tags:
- { name: chill.export_filter, alias: 'activity_date_filter' }
chill.activity.export.reason_filter:
class: Chill\ActivityBundle\Export\Filter\PersonFilters\ActivityReasonFilter
tags:
- { name: chill.export_filter, alias: 'activity_reason_filter' }
chill.activity.export.person_having_an_activity_between_date_filter:
class: Chill\ActivityBundle\Export\Filter\PersonHavingActivityBetweenDateFilter
class: Chill\ActivityBundle\Export\Filter\PersonFilters\PersonHavingActivityBetweenDateFilter
tags:
- #0 register as a filter
name: chill.export_filter
alias: 'activity_person_having_ac_bw_date_filter'
chill.activity.export.locationtype_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter
tags:
- { name: chill.export_filter, alias: 'activity_locationtype_filter' }
chill.activity.export.byuser_filter: # TMS (M2M)
class: Chill\ActivityBundle\Export\Filter\ACPFilters\ByUserFilter
tags:
- { name: chill.export_filter, alias: 'activity_byuser_filter' }
chill.activity.export.emergency_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter
tags:
- { name: chill.export_filter, alias: 'activity_emergency_filter' }
chill.activity.export.sentreceived_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\SentReceivedFilter
tags:
- { name: chill.export_filter, alias: 'activity_sentreceived_filter' }
chill.activity.export.bysocialaction_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter
tags:
- { name: chill.export_filter, alias: 'activity_bysocialaction_filter' }
chill.activity.export.bysocialissue_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialIssueFilter
tags:
- { name: chill.export_filter, alias: 'activity_bysocialissue_filter' }
chill.activity.export.user_filter: # Creator (M2O)
class: Chill\ActivityBundle\Export\Filter\ACPFilters\UserFilter
tags:
- { name: chill.export_filter, alias: 'activity_user_filter' }
chill.activity.export.userscope_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\UserScopeFilter
tags:
- { name: chill.export_filter, alias: 'activity_userscope_filter' }
## Aggregators
chill.activity.export.reason_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ActivityReasonAggregator
class: Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator
tags:
- { name: chill.export_aggregator, alias: activity_reason_aggregator }
@@ -54,3 +122,38 @@ services:
class: Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator
tags:
- { name: chill.export_aggregator, alias: activity_user_aggregator }
chill.activity.export.locationtype_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\LocationTypeAggregator
tags:
- { name: chill.export_aggregator, alias: activity_locationtype_aggregator }
chill.activity.export.date_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\DateAggregator
tags:
- { name: chill.export_aggregator, alias: activity_date_aggregator }
chill.activity.export.byuser_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByUserAggregator
tags:
- { name: chill.export_aggregator, alias: activity_byuser_aggregator }
chill.activity.export.bythirdparty_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator
tags:
- { name: chill.export_aggregator, alias: activity_bythirdparty_aggregator }
chill.activity.export.bysocialaction_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\BySocialActionAggregator
tags:
- { name: chill.export_aggregator, alias: activity_bysocialaction_aggregator }
chill.activity.export.bysocialissue_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\BySocialIssueAggregator
tags:
- { name: chill.export_aggregator, alias: activity_bysocialissue_aggregator }
chill.activity.export.userscope_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\UserScopeAggregator
tags:
- { name: chill.export_aggregator, alias: activity_userscope_aggregator }

View File

@@ -203,18 +203,39 @@ Are you sure you want to remove the activity about "%name%" ?: Êtes-vous sûr d
The activity has been successfully removed.: L'activité a été supprimée.
# exports
Count activities: Nombre d'activités
Count activities by various parameters.: Compte le nombre d'activités enregistrées en fonction de différents paramètres.
Sum activity duration: Total de la durée des activités
Sum activities duration by various parameters.: Additionne la durée des activités en fonction de différents paramètres.
List activities: Liste les activités
Number of activities: Nombre d'activités
Exports of activities linked to a person: Exports des activités liées à une personne
Number of activities linked to a person: Nombre d'activités liées à une personne
Count activities linked to a person: Nombre d'activités
Count activities linked to a person by various parameters.: Compte le nombre d'activités enregistrées et liées à une personne en fonction de différents paramètres.
Sum activity linked to a person duration: Durée des activités
Sum activities linked to a person duration: Durée des activités liés à un usager
Sum activities linked to a person duration by various parameters.: Additionne la durée des activités en fonction de différents paramètres.
List activity linked to a person: Liste les activités
List activities linked to a person: Liste des activités liés à un usager
List activities linked to a person description: Crée la liste des activités en fonction de différents paramètres.
Exports of activities linked to an accompanying period: Exports des activités liées à un parcours
Number of activities linked to an accompanying period: Nombre d'activités liées à un parcours
Count activities linked to an accompanying period: Nombre d'activités
Count activities linked to an accompanying period by various parameters.: Compte le nombre d'activités enregistrées et liées à un parcours en fonction de différents paramètres.
Sum activity linked to an accompanying period duration: Somme de la durée des activités
Sum activities linked to an accompanying period duration: Somme de la durée des activités liées à un parcours
Sum activities linked to an accompanying period duration by various parameters.: Additionne la durée des activités en fonction de différents paramètres.
Sum activity linked to an accompanying period visit duration: Somme de la durée de déplacement des activités
Sum activities linked to an accompanying period visit duration: Somme de la durée de déplacement des activités liées à un parcours
Sum activities linked to an accompanying period visit duration by various parameters.: Additionne la durée de déplacement des activités en fonction de différents paramètres.
Average activity linked to an accompanying period duration: Moyenne de la durée des activités
Average activities linked to an accompanying period duration: Moyenne de la durée des activités liées à un parcours
Average activities linked to an accompanying period duration by various parameters.: Moyenne de la durée des activités en fonction de différents paramètres.
Average activity linked to an accompanying period visit duration: Moyenne de la durée de déplacement des activités
Average activities linked to an accompanying period visit duration: Moyenne de la durée de déplacement des activités liées à un parcours
Average activities linked to an accompanying period visit duration by various parameters.: Moyenne de la durée de déplacement des activités en fonction de différents paramètres.
#filters
Filter by reason: Filtrer par sujet d'activité
Filter by reason: Filtrer les activités par sujet
'Filtered by reasons: only %list%': 'Filtré par sujet: seulement %list%'
'Filtered by activity type: only %list%': "Filtré par type d'activity: seulement %list%"
Filtered by date activity: Filtrer par date d'activité
'Filtered by activity type: only %list%': "Filtré par type d'activité: uniquement %list%"
Filtered by date activity: Filtrer les activités par date
Activities after this date: Activités après cette date
Activities before this date: Activités avant cette date
"Filtered by date of activity: only between %date_from% and %date_to%": "Filtré par date de l'activité: uniquement entre %date_from% et %date_to%"
@@ -226,18 +247,59 @@ Implied in an activity before this date: Impliqué dans une activité avant cett
Filtered by person having an activity between %date_from% and %date_to% with reasons %reasons_name%: Filtré par personnes associées à une activité entre %date_from% et %date_to% avec les sujets %reasons_name%
Activity reasons for those activities: Sujets de ces activités
Filter by activity type: Filtrer par type d'activité
Filter by activity type: Filtrer les activités par type
Filter activity by locationtype: Filtrer les activités par type de localisation
'Filtered activity by locationtype: only %types%': "Filtré par type de localisation: uniquement %types%"
Accepted locationtype: Types de localisation
Filter activity by linked users: Filtrer les activités par TMS
'Filtered activity by linked users: only %users%': "Filtré par TMS: uniquement %users%"
Accepted users: TMS(s)
Filter activity by emergency: Filtrer les activités par urgence
'Filtered activity by emergency: only %emergency%': "Filtré par urgence: uniquement si %emergency%"
activity is emergency: l'activité est urgente
activity is not emergency: l'activité n'est pas urgente
Filter activity by sentreceived: Filtrer les activités par envoyé/reçu
'Filtered activity by sentreceived: only %sentreceived%': "Filtré par envoyé/reçu: uniquement %sentreceived%"
Accepted sentreceived: ''
is sent: envoyé
is received: reçu
Filter activity by linked socialaction: Filtrer les activités par action liée
'Filtered activity by linked socialaction: only %actions%': "Filtré par action liée: uniquement %actions%"
Filter activity by linked socialissue: Filtrer les activités par problématique liée
'Filtered activity by linked socialissue: only %issues%': "Filtré par problématique liée: uniquement %issues%"
Filter activity by user: Filtrer les activités par créateur
'Filtered activity by user: only %users%': "Filtré par créateur: uniquement %users%"
Creators: Créateurs
Filter activity by userscope: Filtrer les activités par service du créateur
'Filtered activity by userscope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%"
Accepted userscope: Services
#aggregators
Activity type: Type d'activité
Activity user: Utilisateur lié à l'activity
Activity user: Utilisateur lié à l'activité
By reason: Par sujet
By category of reason: Par catégorie de sujet
Reason's level: Niveau du sujet
Group by reasons: Sujet d'activité
Aggregate by activity user: Aggréger par utilisateur lié à l'activité
Aggregate by activity type: Aggréger par type d'activité
Aggregate by activity reason: Aggréger par sujet de l'activité
Aggregate by activity user: Grouper les activités par utilisateur
Aggregate by activity type: Grouper les activités par type
Aggregate by activity reason: Grouper les activités par sujet
Group activity by locationtype: Grouper les activités par type de localisation
Group activity by date: Grouper les activités par date
Frequency: Fréquence
by month: Par mois
by week: Par semaine
for week: Semaine
by year: Par année
in year: En
Group activity by linked users: Grouper les activités par TMS impliqué
Group activity by linked thirdparties: Grouper les activités par tiers impliqué
Accepted thirdparty: Tiers impliqué
Group activity by linked socialaction: Grouper les activités par action liée
Group activity by linked socialissue: Grouper les activités par problématique liée
Group activity by userscope: Grouper les activités par service du créateur
Last activities: Les dernières activités

View File

@@ -195,7 +195,7 @@ Number of activities: Nombre d'activités
#filters
Filter by reason: Filtrer par sujet d'activité
'Filtered by reasons: only %list%': 'Filtré par sujet: seulement %list%'
'Filtered by activity type: only %list%': "Filtré par type d'activity: seulement %list%"
'Filtered by activity type: only %list%': "Filtré par type d'activity: uniquement %list%"
Filtered by date activity: Filtrer par date d'activité
Activities after this date: Activités après cette date
Activities before this date: Activités avant cette date
@@ -217,9 +217,9 @@ By reason: Par sujet
By category of reason: Par catégorie de sujet
Reason's level: Niveau du sujet
Group by reasons: Sujet d'activité
Aggregate by activity user: Aggréger par utilisateur lié à l'activité
Aggregate by activity type: Aggréger par type d'activité
Aggregate by activity reason: Aggréger par sujet de l'activité
Aggregate by activity user: Grouper par utilisateur lié à l'activité
Aggregate by activity type: Grouper par type d'activité
Aggregate by activity reason: Grouper par sujet de l'activité
Last activities: Les dernières activités

View File

@@ -29,44 +29,58 @@ class ConfigRepository
$this->charges = $charges;
}
public function getChargesKeys(): array
public function getChargesKeys(bool $onlyActive = false): array
{
return array_map(static function ($element) { return $element['key']; }, $this->charges);
return array_map(static function ($element) { return $element['key']; }, $this->getCharges($onlyActive));
}
/**
* @return array where keys are the resource'key and label the ressource label
*/
public function getChargesLabels()
public function getChargesLabels(bool $onlyActive = false)
{
$charges = [];
foreach ($this->charges as $definition) {
foreach ($this->getCharges($onlyActive) as $definition) {
$charges[$definition['key']] = $this->normalizeLabel($definition['labels']);
}
return $charges;
}
public function getResourcesKeys(): array
public function getResourcesKeys(bool $onlyActive = false): array
{
return array_map(static function ($element) { return $element['key']; }, $this->resources);
return array_map(static function ($element) { return $element['key']; }, $this->getResources($onlyActive));
}
/**
* @return array where keys are the resource'key and label the ressource label
*/
public function getResourcesLabels()
public function getResourcesLabels(bool $onlyActive = false)
{
$resources = [];
foreach ($this->resources as $definition) {
foreach ($this->getResources($onlyActive) as $definition) {
$resources[$definition['key']] = $this->normalizeLabel($definition['labels']);
}
return $resources;
}
private function getCharges(bool $onlyActive = false): array
{
return $onlyActive ?
array_filter($this->charges, function ($el) { return $el['active']; })
: $this->charges;
}
private function getResources(bool $onlyActive = false): array
{
return $onlyActive ?
array_filter($this->resources, function ($el) { return $el['active']; })
: $this->resources;
}
private function normalizeLabel($labels)
{
$normalizedLabels = [];

View File

@@ -14,11 +14,6 @@ namespace Chill\BudgetBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
*/
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
@@ -37,6 +32,7 @@ class Configuration implements ConfigurationInterface
->info('the key stored in database')
->example('salary')
->end()
->booleanNode('active')->defaultTrue()->end()
->arrayNode('labels')->isRequired()->requiresAtLeastOneElement()
->arrayPrototype()
->children()
@@ -59,6 +55,7 @@ class Configuration implements ConfigurationInterface
->info('the key stored in database')
->example('salary')
->end()
->booleanNode('active')->defaultTrue()->end()
->arrayNode('labels')->isRequired()->requiresAtLeastOneElement()
->arrayPrototype()
->children()

View File

@@ -103,7 +103,7 @@ class ChargeType extends AbstractType
private function getTypes()
{
$charges = $this->configRepository
->getChargesLabels();
->getChargesLabels(true);
// rewrite labels to filter in language
foreach ($charges as $key => $labels) {

View File

@@ -87,7 +87,7 @@ class ResourceType extends AbstractType
private function getTypes()
{
$resources = $this->configRepository
->getResourcesLabels();
->getResourcesLabels(true);
// rewrite labels to filter in language
foreach ($resources as $key => $labels) {

View File

@@ -32,6 +32,7 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
$loader->load('services/exports.yaml');
$loader->load('services/controller.yml');
$loader->load('services/fixtures.yml');
$loader->load('services/form.yml');

View File

@@ -0,0 +1,79 @@
<?php
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final class AgentAggregator implements AggregatorInterface
{
private UserRepository $userRepository;
private UserRender $userRender;
public function __construct(
UserRepository $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$qb->addSelect('u.id AS agent_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('agent_aggregator');
} else {
$qb->groupBy('agent_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data): \Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'Agent';
}
$r = $this->userRepository->find($value);
return $this->userRender->renderString($r, []);
};
}
public function getQueryKeys($data): array
{
return ['agent_aggregator'];
}
public function getTitle(): string
{
return 'Group by agent';
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CancelReasonRepository;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class CancelReasonAggregator implements AggregatorInterface
{
private CancelReasonRepository $cancelReasonRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
CancelReasonRepository $cancelReasonRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->cancelReasonRepository = $cancelReasonRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function getLabels($key, array $values, $data): \Closure
{
return function($value): string {
if ($value === '_header') {
return 'Cancel reason';
}
$j = $this->cancelReasonRepository->find($value);
return $this->translatableStringHelper->localize(
$j->getName()
);
};
}
public function getQueryKeys($data): array
{
return ['cancel_reason_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group by cancel reason';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
// TODO: still needs to take into account appointments without a cancel reason somehow
$qb->join('cal.cancelReason', 'cr');
$qb->addSelect('IDENTITY(cal.cancelReason) as cancel_reason_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('cancel_reason_aggregator');
} else {
$qb->groupBy('cancel_reason_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserJobRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final class JobAggregator implements AggregatorInterface
{
private UserJobRepository $jobRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
UserJobRepository $jobRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->jobRepository = $jobRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function getLabels($key, array $values, $data): \Closure
{
return function($value): string {
if ($value === '_header') {
return 'Job';
}
$j = $this->jobRepository->find($value);
return $this->translatableStringHelper->localize(
$j->getLabel()
);
};
}
public function getQueryKeys($data): array
{
return ['job_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group by agent job';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$qb->addSelect('IDENTITY(u.userJob) as job_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('job_aggregator');
} else {
$qb->groupBy('job_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\LocationRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
final class LocationAggregator implements AggregatorInterface
{
private LocationRepository $locationRepository;
public function __construct(
LocationRepository $locationRepository
) {
$this->locationRepository = $locationRepository;
}
public function getLabels($key, array $values, $data): \Closure
{
return function($value): string {
if ($value === '_header') {
return 'Location';
}
$l = $this->locationRepository->find($value);
return $l->getName();
};
}
public function getQueryKeys($data): array
{
return ['location_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group by location';
}
public function addRole(): ?Role
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.location', 'l');
$qb->addSelect('IDENTITY(cal.location) as location_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('location_aggregator');
} else {
$qb->groupBy('location_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\LocationTypeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final class LocationTypeAggregator implements AggregatorInterface
{
private LocationTypeRepository $locationTypeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
LocationTypeRepository $locationTypeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->locationTypeRepository = $locationTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function getLabels($key, array $values, $data): \Closure
{
return function($value): string {
if ($value === '_header') {
return 'Location type';
}
$j = $this->locationTypeRepository->find($value);
return $this->translatableStringHelper->localize(
$j->getTitle()
);
};
}
public function getQueryKeys($data): array
{
return ['location_type_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group by location type';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.location', 'l');
$qb->addSelect('IDENTITY(l.locationType) as location_type_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('location_type_aggregator');
} else {
$qb->groupBy('location_type_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class MonthYearAggregator implements AggregatorInterface
{
public function getQueryKeys($data): array
{
return ['month_year_aggregator'];
}
public function getLabels($key, array $values, $data): Closure
{
return function($value): string {
if ($value === '_header') {
return 'by month and year';
}
$month = substr($value,0, 2);
$year = substr($value, 3, 4);
return strftime('%B %G', mktime(0, 0, 0, $month, '1', $year));
};
}
public function buildForm(FormBuilderInterface $builder)
{
// No form needed
}
public function getTitle(): string
{
return 'Group by month and year';
}
public function addRole(): ?Role
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->addSelect("to_char(cal.startDate, 'MM-YYYY') AS month_year_aggregator");
// $qb->addSelect("extract(month from age(cal.startDate, cal.endDate)) AS month_aggregator");
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('month_year_aggregator');
} else {
$qb->groupBy('month_year_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final class ScopeAggregator implements AggregatorInterface
{
private ScopeRepository $scopeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
ScopeRepository $scopeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function getLabels($key, array $values, $data): \Closure
{
return function ($value): string {
if ($value === '_header') {
return 'Scope';
}
$s = $this->scopeRepository->find($value);
return $this->translatableStringHelper->localize(
$s->getName()
);
};
}
public function getQueryKeys($data): array
{
return ['scope_aggregator'];
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getTitle(): string
{
return 'Group by agent scope';
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$qb->addSelect('IDENTITY(u.mainScope) as scope_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('scope_aggregator');
} else {
$qb->groupBy('scope_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Export;
/**
* This class declare constants used for the export framework.
*/
abstract class Declarations
{
public const CALENDAR_TYPE = 'calendar';
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Chill\CalendarBundle\Export\Export;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\CalendarBundle\Export\Declarations;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Security\Core\Role\Role;
class CountAppointments implements ExportInterface, GroupedExportInterface
{
private CalendarRepository $calendarRepository;
public function __construct(CalendarRepository $calendarRepository)
{
$this->calendarRepository = $calendarRepository;
}
public function buildForm(FormBuilderInterface $builder)
{
// No form necessary
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Count appointments by various parameters.';
}
public function getLabels($key, array $values, $data): \Closure
{
if ('export_result' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
}
public function getQueryKeys($data): array
{
return ['export_result'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Count appointments';
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
/**
* Initiate the query.
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb->select('COUNT(cal.id) AS export_result');
return $qb;
}
public function requiredRole(): Role
{
// which role should we give here?
return new Role(PersonVoter::STATS);
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE,
];
}
public function getGroup(): string
{
return 'Exports of calendar';
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Chill\CalendarBundle\Export\Export;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class StatAppointmentAvgDuration implements ExportInterface, GroupedExportInterface
{
private CalendarRepository $calendarRepository;
public function __construct(
CalendarRepository $calendarRepository
) {
$this->calendarRepository = $calendarRepository;
}
public function buildForm(FormBuilderInterface $builder): void
{
// no form needed
}
public function getTitle(): string
{
return 'Average appointment duration';
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Get the average of appointment duration according to various filters';
}
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
}
public function getQueryKeys($data): array
{
return ['export_result'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb
->select('AVG(cal.endDate - cal.startDate) AS export_result');
return $qb;
}
public function requiredRole(): Role
{
return new Role(AccompanyingPeriodVoter::STATS);
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE
];
}
public function getGroup(): string
{
return 'Exports of calendar';
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace Chill\CalendarBundle\Export\Export;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class StatAppointmentSumDuration implements ExportInterface, GroupedExportInterface
{
private CalendarRepository $calendarRepository;
public function __construct(
CalendarRepository $calendarRepository
) {
$this->calendarRepository = $calendarRepository;
}
public function buildForm(FormBuilderInterface $builder): void
{
// no form needed
}
public function getTitle(): string
{
return 'Sum of appointment durations';
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Get the sum of appointment durations according to various filters';
}
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
}
public function getQueryKeys($data): array
{
return ['export_result'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb
->select('SUM(cal.endDate - cal.startDate) AS export_result');
return $qb;
}
public function requiredRole(): Role
{
return new Role(AccompanyingPeriodVoter::STATS);
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE
];
}
public function getGroup(): string
{
return 'Exports of calendar';
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Chill\CalendarBundle\Export\Filter;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class AgentFilter implements FilterInterface
{
private UserRender $userRender;
public function __construct(UserRender $userRender)
{
$this->userRender = $userRender;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_agents', EntityType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true,
'expanded' => true
]);
}
public function getTitle(): string
{
return 'Filter by agent';
}
public function describeAction($data, $format = 'string'): array
{
$users = [];
foreach ($data['accepted_agents'] as $r) {
$users[] = $r;
}
return [
'Filtered by agent: only %agents%', [
'%agents' => implode(", ou ", $users)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('cal.user', ':agents');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('agents', $data['accepted_agents']);
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Chill\CalendarBundle\Export\Filter;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class BetweenDatesFilter implements FilterInterface
{
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('date_from', ChillDateType::class, [
'data' => new \DateTime(),
])
->add('date_to', ChillDateType::class, [
'data' => new \DateTime(),
])
;
}
public function getTitle(): string
{
return 'Filter by appointments between certain dates';
}
public function describeAction($data, $format = 'string'): array
{
return ['Filtered by appointments between %dateFrom% and %dateTo%', [
'%dateFrom%' => $data['date_from']->format('d-m-Y'),
'%dateTo%' => $data['date_to']->format('d-m-Y'),
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->andX(
$qb->expr()->gte('cal.startDate', ':dateFrom'),
$qb->expr()->lte('cal.endDate', ':dateTo')
);
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('dateFrom', $data['date_from']);
// modify dateTo so that entire day is also taken into account up until the beginning of the next day.
$qb->setParameter('dateTo', $data['date_to']->modify('+1 day'));
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Export\Filter;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class JobFilter implements FilterInterface
{
protected TranslatorInterface $translator;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper
) {
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('job', EntityType::class, [
'class' => UserJob::class,
'choice_label' => function (UserJob $j) {
return $this->translatableStringHelper->localize(
$j->getLabel()
);
},
'multiple' => true,
'expanded' => true
]);
}
public function describeAction($data, $format = 'string'): array
{
$userJobs = [];
foreach ($data['job'] as $j) {
$userJobs[] = $this->translatableStringHelper->localize(
$j->getLabel());
}
return ['Filtered by agent job: only %jobs%', [
'%jobs%' => implode(', ou ', $userJobs)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('u.userJob', ':job');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('job', $data['job']);
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function getTitle(): string
{
return 'Filter by agent job';
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Export\Filter;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ScopeFilter implements FilterInterface
{
protected TranslatorInterface $translator;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper
) {
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('scope', EntityType::class, [
'class' => Scope::class,
'choice_label' => function (Scope $s) {
return $this->translatableStringHelper->localize(
$s->getName()
);
},
'multiple' => true,
'expanded' => true
]);
}
public function describeAction($data, $format = 'string')
{
$scopes = [];
foreach ($data['scope'] as $s) {
$scopes[] = $this->translatableStringHelper->localize(
$s->getName());
}
return ['Filtered by agent scope: only %scopes%', [
'%scopes%' => implode(', ou ', $scopes)
]];
}
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('u.mainScope', ':scope');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('scope', $data['scope']);
}
public function applyOn()
{
return Declarations::CALENDAR_TYPE;
}
public function getTitle()
{
return 'Filter by agent scope';
}
}

View File

@@ -0,0 +1,104 @@
services:
## Indicators
chill.calendar.export.count_appointments:
class: Chill\CalendarBundle\Export\Export\CountAppointments
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: count_appointments }
chill.calendar.export.average_duration_appointments:
class: Chill\CalendarBundle\Export\Export\StatAppointmentAvgDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: average_duration_appointments }
chill.calendar.export.sum_duration_appointments:
class: Chill\CalendarBundle\Export\Export\StatAppointmentSumDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: sum_duration_appointments }
## Filters
chill.calendar.export.agent_filter:
class: Chill\CalendarBundle\Export\Filter\AgentFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: agent_filter }
chill.calendar.export.job_filter:
class: Chill\CalendarBundle\Export\Filter\JobFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: job_filter }
chill.calendar.export.scope_filter:
class: Chill\CalendarBundle\Export\Filter\ScopeFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: scope_filter }
chill.calendar.export.between_dates_filter:
class: Chill\CalendarBundle\Export\Filter\BetweenDatesFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: between_dates_filter }
## Aggregator
chill.calendar.export.agent_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\AgentAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: agent_aggregator }
chill.calendar.export.job_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\JobAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: job_aggregator }
chill.calendar.export.scope_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\ScopeAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: scope_aggregator }
chill.calendar.export.location_type_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\LocationTypeAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: location_type_aggregator }
chill.calendar.export.location_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\LocationAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: location_aggregator }
chill.calendar.export.cancel_reason_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\CancelReasonAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: cancel_reason_aggregator }
chill.calendar.export.month_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\MonthYearAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: month_aggregator }

View File

@@ -38,3 +38,36 @@ crud:
add_new: Ajouter un nouveau
title_new: Nouveau motif d'annulation
title_edit: Modifier le motif d'annulation
# exports
Exports of calendar: Exports des rendez-vous
Count appointments: Nombre de rendez-vous
Count appointments by various parameters.: Compte le nombre de rendez-vous en fonction de différents paramètres.
Average appointment duration: Moyenne de la durée des rendez-vous
Get the average of appointment duration according to various filters: Calcule la moyenne des durées des rendez-vous en fonction de différents paramètres.
Sum of appointment durations: Somme de la durée des rendez-vous
Get the sum of appointment durations according to various filters: Calcule la somme des durées des rendez-vous en fonction de différents paramètres.
'Filtered by agent: only %agents%': "Filtré par agents: uniquement %agents%"
Filter by agent: Filtrer par agents
Filter by agent job: Filtrer par métiers des agents
'Filtered by agent job: only %jobs%': 'Filtré par métiers des agents: uniquement les %jobs%'
Filter by agent scope: Filtrer par services des agents
'Filtered by agent scope: only %scopes%': 'Filtré par services des agents: uniquement les services %scopes%'
Filter by appointments between certain dates: Filtrer par date du rendez-vous
'Filtered by appointments between %dateFrom% and %dateTo%': 'Filtré par rendez-vous entre %dateFrom% et %dateTo%'
Group by agent: Grouper par agent
Group by agent job: Grouper par métier de l'agent
Group by agent scope: Grouper par service de l'agent
Group by location type: Grouper par type de localisation
Group by location: Grouper par lieu de rendez-vous
Group by cancel reason: Grouper par motif d'annulation
Group by month and year: Grouper par mois et année
Scope: Service
Job: Métier
Location type: Type de localisation
Location: Lieu de rendez-vous
by month and year: Par mois et année

View File

@@ -188,7 +188,7 @@ class CustomFieldChoice extends AbstractCustomField
$choices = [];
foreach ($cf->getOptions()[self::CHOICES] as $choice) {
if (false === $choices['active']) {
if (false === $choice['active']) {
continue;
}
$choices[$choice['slug']] = $this->translatableStringHelper

View File

@@ -0,0 +1,52 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Command;
use Chill\MainBundle\Service\Import\AddressReferenceBEFromBestAddress;
use Chill\MainBundle\Service\Import\PostalCodeBEFromBestAddress;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LoadAddressesBEFromBestAddressCommand extends Command
{
private AddressReferenceBEFromBestAddress $addressImporter;
private PostalCodeBEFromBestAddress $postalCodeBEFromBestAddressImporter;
public function __construct(
AddressReferenceBEFromBestAddress $addressImporter,
PostalCodeBEFromBestAddress $postalCodeBEFromBestAddressImporter
) {
parent::__construct();
$this->addressImporter = $addressImporter;
$this->postalCodeBEFromBestAddressImporter = $postalCodeBEFromBestAddressImporter;
}
protected function configure()
{
$this
->setName('chill:main:address-ref-from-best-addresses')
->addArgument('lang', InputArgument::REQUIRED)
->addArgument('list', InputArgument::IS_ARRAY, 'The list to add');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->postalCodeBEFromBestAddressImporter->import();
$this->addressImporter->import($input->getArgument('lang'), $input->getArgument('list'));
return 0;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Command;
use Chill\MainBundle\Service\Import\AddressReferenceFromBano;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LoadAddressesFRFromBANOCommand extends Command
{
private AddressReferenceFromBano $addressReferenceFromBano;
public function __construct(AddressReferenceFromBano $addressReferenceFromBano)
{
parent::__construct();
$this->addressReferenceFromBano = $addressReferenceFromBano;
}
protected function configure()
{
$this->setName('chill:main:address-ref-from-bano')
->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers')
->setDescription('Import addresses from bano (see https://bano.openstreetmap.fr');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
foreach ($input->getArgument('departementNo') as $departementNo) {
$output->writeln('Import addresses for ' . $departementNo);
$this->addressReferenceFromBano->import($departementNo);
}
return 0;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Command;
use Chill\MainBundle\Service\Import\PostalCodeFRFromOpenData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LoadPostalCodeFR extends Command
{
private PostalCodeFRFromOpenData $loader;
public function __construct(PostalCodeFRFromOpenData $loader)
{
$this->loader = $loader;
parent::__construct();
}
public function configure(): void
{
$this->setName('chill:main:postal-code:load:FR')
->setDescription('Load France\'s postal code from online open data');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$this->loader->import();
return 0;
}
}

View File

@@ -86,6 +86,9 @@ class ExportController extends AbstractController
{
/** @var \Chill\MainBundle\Export\ExportManager $exportManager */
$exportManager = $this->exportManager;
$export = $exportManager->getExport($alias);
$key = $request->query->get('key', null);
[$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key);
@@ -100,7 +103,8 @@ class ExportController extends AbstractController
$viewVariables = [
'alias' => $alias,
'export' => $exportManager->getExport($alias),
'export' => $export,
'export_group' => $this->getExportGroup($export),
];
if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) {
@@ -316,6 +320,7 @@ class ExportController extends AbstractController
'form' => $form->createView(),
'export_alias' => $alias,
'export' => $export,
'export_group' => $this->getExportGroup($export),
]);
}
@@ -371,6 +376,7 @@ class ExportController extends AbstractController
[
'form' => $form->createView(),
'export' => $export,
'export_group' => $this->getExportGroup($export),
]
);
}
@@ -514,6 +520,7 @@ class ExportController extends AbstractController
[
'form' => $form->createView(),
'export' => $export,
'export_group' => $this->getExportGroup($export),
]
);
}
@@ -565,4 +572,19 @@ class ExportController extends AbstractController
throw new LogicException("the step {$step} is not defined.");
}
}
private function getExportGroup($target): string
{
$exportManager = $this->exportManager;
$groups = $exportManager->getExportsGrouped(true);
foreach ($groups as $group => $array) {
foreach ($array as $alias => $export) {
if ($export === $target) {
return $group;
}
}
}
}
}

View File

@@ -318,19 +318,12 @@ class WorkflowController extends AbstractController
);
}
// TODO symfony 5: add those "future" on context ($workflow->apply($entityWorkflow, $transition, $context)
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData();
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData();
$workflow->apply($entityWorkflow, $transition);
foreach ($transitionForm['future_dest_users']->getData() as $user) {
$entityWorkflow->getCurrentStep()->addDestUser($user);
}
foreach ($transitionForm['future_dest_emails']->getData() as $email) {
$entityWorkflow->getCurrentStep()->addDestEmail($email);
}
$this->entityManager->flush();
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);

View File

@@ -22,6 +22,7 @@ use Chill\MainBundle\Controller\UserController;
use Chill\MainBundle\Controller\UserJobApiController;
use Chill\MainBundle\Controller\UserJobController;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\Doctrine\DQL\Extract;
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
@@ -31,6 +32,7 @@ use Chill\MainBundle\Doctrine\DQL\Replace;
use Chill\MainBundle\Doctrine\DQL\Similarity;
use Chill\MainBundle\Doctrine\DQL\STContains;
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
use Chill\MainBundle\Doctrine\DQL\ToChar;
use Chill\MainBundle\Doctrine\DQL\Unaccent;
use Chill\MainBundle\Doctrine\ORM\Hydration\FlatHierarchyEntityHydrator;
use Chill\MainBundle\Doctrine\Type\NativeDateIntervalType;
@@ -238,6 +240,10 @@ class ChillMainExtension extends Extension implements
'ST_CONTAINS' => STContains::class,
'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class,
],
'datetime_functions' => [
'EXTRACT' => Extract::class,
'TO_CHAR' => ToChar::class,
],
],
'hydrators' => [
'chill_flat_hierarchy_list' => FlatHierarchyEntityHydrator::class,

View File

@@ -0,0 +1,54 @@
<?php
namespace Chill\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\DateDiffFunction;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Extract postgresql function
* https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
*
* Usage : EXTRACT(field FROM timestamp)
* TODO allow interval usage -> EXTRACT(field FROM interval)
*/
class Extract extends FunctionNode
{
private string $field;
private $value;
//private PathExpression $value;
//private FunctionNode $value;
//private DateDiffFunction $value;
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
'EXTRACT(%s FROM %s)',
$this->field,
$this->value->dispatch($sqlWalker)
);
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_IDENTIFIER);
$this->field = $parser->getLexer()->token['value'];
$parser->match(Lexer::T_FROM);
//$this->value = $parser->ScalarExpression();
$this->value = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Chill\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Usage : TO_CHAR(datetime, fmt)
*/
class ToChar extends FunctionNode
{
private $datetime;
private $fmt;
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
'TO_CHAR(%s, %s)',
$sqlWalker->walkArithmeticPrimary($this->datetime),
$sqlWalker->walkArithmeticPrimary($this->fmt)
);
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->datetime = $parser->ArithmeticExpression();
$parser->match(Lexer::T_COMMA);
$this->fmt = $parser->StringExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -20,6 +20,9 @@ use Symfony\Component\Serializer\Annotation\Groups;
* @ORM\Entity
* @ORM\Table(name="chill_main_address_reference", indexes={
* @ORM\Index(name="address_refid", columns={"refId"})
* },
* uniqueConstraints={
* @ORM\UniqueConstraint(name="chill_main_address_reference_unicity", columns={"refId", "source"})
* })
* @ORM\HasLifecycleCallbacks
*/

View File

@@ -0,0 +1,72 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="chill_main_geographical_unit")
* @ORM\Entity
*/
class GeographicalUnit
{
/**
* @ORM\Column(type="text", nullable=true)
*/
private $geom;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $layerName;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $unitName;
public function getId(): ?int
{
return $this->id;
}
public function getLayerName(): ?string
{
return $this->layerName;
}
public function getUnitName(): ?string
{
return $this->unitName;
}
public function setLayerName(?string $layerName): self
{
$this->layerName = $layerName;
return $this;
}
public function setUnitName(?string $unitName): self
{
$this->unitName = $unitName;
return $this;
}
}

View File

@@ -12,6 +12,11 @@ declare(strict_types=1);
namespace Chill\MainBundle\Entity;
use Chill\MainBundle\Doctrine\Model\Point;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
@@ -21,6 +26,10 @@ use Symfony\Component\Serializer\Annotation\Groups;
* @ORM\Entity
* @ORM\Table(
* name="chill_main_postal_code",
* uniqueConstraints={
* @ORM\UniqueConstraint(name="postal_code_import_unicity", columns={"code", "refpostalcodeid", "postalcodesource"},
* options={"where": "refpostalcodeid is not null"})
* },
* indexes={
* @ORM\Index(name="search_name_code", columns={"code", "label"}),
* @ORM\Index(name="search_by_reference_code", columns={"code", "refpostalcodeid"})
@@ -28,8 +37,12 @@ use Symfony\Component\Serializer\Annotation\Groups;
*
* @ORM\HasLifecycleCallbacks
*/
class PostalCode
class PostalCode implements TrackUpdateInterface, TrackCreationInterface
{
use TrackCreationTrait;
use TrackUpdateTrait;
/**
* This is an internal column which is populated by database.
*
@@ -63,6 +76,11 @@ class PostalCode
*/
private $country;
/**
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": null})
*/
private ?DateTimeImmutable $deletedAt = null;
/**
* @var int
*

View File

@@ -568,6 +568,10 @@ class ExportManager
'role' => $role->getRole(),
]);
///// Bypasse les autorisations qui empêche d'afficher les nouveaux exports
return true;
///// TODO supprimer le return true
return false;
}
}

View File

@@ -449,7 +449,9 @@ class SpreadSheetFormatter implements FormatterInterface
protected function getTitle()
{
return $this->translator->trans($this->export->getTitle());
$title = $this->translator->trans($this->export->getTitle());
return substr($title, 0, 30) . '…';
}
protected function initializeCache($key)

View File

@@ -59,7 +59,7 @@ class CommentType extends AbstractType
$view->vars = array_replace(
$view->vars,
[
'hideLabel' => true,
'fullWidth' => true,
]
);
}

View File

@@ -46,7 +46,7 @@ class PrivateCommentType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['hideLabel'] = true;
$view->vars['fullWidth'] = true;
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -108,7 +108,7 @@ class ScopePickerType extends AbstractType
$view->vars = array_replace(
$view->vars,
[
'hideLabel' => true,
'fullWidth' => true,
]
);
}

View File

@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Country;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
final class CountryRepository implements ObjectRepository
@@ -25,6 +26,11 @@ final class CountryRepository implements ObjectRepository
$this->repository = $entityManager->getRepository(Country::class);
}
public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder
{
return $this->repository->createQueryBuilder($alias, $indexBy);
}
public function find($id, $lockMode = null, $lockVersion = null): ?Country
{
return $this->repository->find($id, $lockMode, $lockVersion);

View File

@@ -46,7 +46,6 @@ require('../lib/collection/index.js');
require('../lib/breadcrumb/index.js');
require('../lib/download-report/index.js');
require('../lib/select_interactive_loading/index.js');
require('../lib/export-list/index.js');
//require('../lib/show_hide/index.js');
//require('../lib/tabs/index.js');

View File

@@ -99,11 +99,14 @@ div.flex-table {
div.item-bloc {
flex-direction: column;
&:nth-child(even) {
background-color: $gray-200;
&:not(.no-altern) { // class to avoid even/odd
.chill-user-quote {
background-color: shade-color($gray-200, 5%)
&:nth-child(even) {
background-color: $gray-200;
.chill-user-quote {
background-color: shade-color($gray-200, 5%)
}
}
}

View File

@@ -1,20 +0,0 @@
.container-export {
margin-left: 1rem;
margin-right: 1rem;
.export-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.export-list__element {
min-width: 18rem;
max-width: 20rem;
padding: 1rem;
margin: 1rem;
flex-grow: 1;
border: 1px solid var(--chill-gray);
}
}
}

View File

@@ -1 +0,0 @@
require('./export-list.scss');

View File

@@ -1,14 +1,23 @@
// Configuration
/*
* This shared file is used in Chill sass sheets and Vue sass styles to use some bootstrap/chill variables.
* Search on @import 'ChillMainAssets/module/bootstrap/shared';
*/
// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
@import "bootstrap/scss/functions";
/*
* Replaced by CHILL variables
* it is necessary to keep the possibility of making a diff with original !
* original: "bootstrap/scss/variables";
*/
// 2. Include any default variable overrides here
@import "custom/_variables";
// 3. Include remainder of required Bootstrap stylesheets
@import "bootstrap/scss/variables";
// 4. Include any default map overrides here
@import "custom/_maps";
// 5. Include remainder of required parts
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/root";

View File

@@ -1,18 +1,8 @@
/*!
* Bootstrap v5.0.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*
* Enable / disable bootstrap assets
*/
// Bootstrap configuration files are shared with chill entrypoint
// Some Bootstrap variables and configuration files are shared with chill entrypoint
@import "shared";
// scss-docs-start import-stack
// Layout & components
@import "bootstrap/scss/root";
// 6. Optionally include any other parts as needed
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
@import "bootstrap/scss/images";
@@ -42,14 +32,11 @@
@import "bootstrap/scss/carousel";
@import "bootstrap/scss/spinners";
@import "bootstrap/scss/offcanvas";
// Helpers
@import "bootstrap/scss/helpers";
// Utilities
// 7. Optionally include utilities API last to generate classes based on the Sass map in
@import "bootstrap/scss/utilities/api";
// scss-docs-end import-stack
// CHILL custom
// 8. Add additional custom code here
@import "custom";
@import "custom/_debug";

View File

@@ -0,0 +1,69 @@
//// CHILL OVERRIDE DEFAULT BOOTSTRAP COLORS MAP
// 'beige' variations
$beige-100: tint-color($beige, 80%);
$beige-200: tint-color($beige, 60%);
$beige-300: tint-color($beige, 40%);
$beige-400: tint-color($beige, 20%);
$beige-500: $beige;
$beige-600: shade-color($beige, 20%);
$beige-700: shade-color($beige, 40%);
$beige-800: shade-color($beige, 60%);
$beige-900: shade-color($beige, 80%);
$chill-blue: $blue;
$chill-green: $green;
$chill-green-dark: $green-600;
$chill-orange: $orange;
$chill-yellow: $yellow;
$chill-red: $red;
$chill-beige: $beige;
$chill-pink: $pink;
$chill-dark-gray: $gray-800;
$chill-gray: $gray-600;
$chill-l-gray: $gray-400;
$chill-ll-gray: $gray-300;
$chill-light-gray: $gray-200;
$chill-llight-gray: $gray-100;
$colors: (
"blue": $blue,
"green": $green,
"green-dark": $green-600,
"yellow": $yellow,
"orange": $orange,
"red": $red,
"pink": $pink,
"beige": $beige,
"white": $white,
"gray": $gray-600,
"dark": $gray-800,
"black": $black
);
$chill-colors: (
"chill-blue": $chill-blue,
"chill-green": $chill-green,
"chill-green-dark": $chill-green-dark,
"chill-orange": $chill-orange,
"chill-yellow": $chill-yellow,
"chill-red": $chill-red,
"chill-beige": $chill-beige,
"chill-pink": $chill-pink,
"chill-dark-gray": $chill-dark-gray,
"chill-gray": $chill-gray,
"chill-l-gray": $chill-l-gray,
"chill-ll-gray": $chill-ll-gray,
"chill-light-gray": $chill-light-gray,
"chill-llight-gray": $chill-llight-gray,
);
// Merge the maps
$theme-colors: map-merge($theme-colors, $chill-colors);
// Chill color classes
@each $color, $value in $chill-colors {
.#{$color} {
color: $value;
}
}

View File

@@ -1,190 +0,0 @@
/*
* NOTE 2021.04
* scss/chill.scss is the main sass file for the new chill.2
* scratch will be replaced by bootstrap, please avoid to edit in modules/scratch/_custom.scss
*/
// YOUR CUSTOM SCSS
@import 'custom/config/colors';
@import 'custom/config/variables';
@import 'custom/fonts';
@import 'custom/timeline';
@import 'custom/mixins/entity';
@import 'custom/report';
@import 'custom/person';
@import 'custom/pagination';
@import 'custom/custom-fields';
@import 'custom/address';
@import 'custom/record_actions';
@import 'custom/flash_messages';
@import 'custom/box';
html,body {
min-height:100%;
font-family: 'Open Sans';
}
header {
position: relative;
}
#content_conainter {
position: relative;
min-height: calc(100% - 145px);
}
#content_conainter:before {
bottom: 0;
content: "";
left: 0;
opacity: 0.1;
position: absolute;
right: 0;
top: 0;
z-index: -1;
//background-image: url('./../../img/background/desert.jpg');
background-attachment: fixed;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
}
/* CUSTOM FIELDS -> */
.cf-title {
font-size: 2em;
}
.cf-subtitle {
font-size: 1.5em;
}
/* <- CUSTOM FIELDS */
@each $len in 11, 15 {
ul.submenu.width-#{$len}-em {
min-width: #{$len}em;
}
}
.content {
padding-top: 1em;
padding-bottom: 1em;
}
.select2 {
width: 100%;
}
ul.custom_fields.choice li {
list-style:none;
}
.errors {
color: $red;
}
.footer {
p {
font-family: 'Open Sans';
font-weight: 300;
}
a {
color: white;
text-decoration: underline;
}
}
// inline time input
.time_compound {
input[type=text], select {
width: 4em;
display: inline-block;
text-align: center;
}
.separator {
margin-left: 0.2em;
margin-right: 0.2em;
}
}
.open_sansbold {
font-family: 'Open Sans';
font-weight: bold;
}
dd {
margin-left: 0;
}
dt {
font-family: 'Open Sans';
font-weight: 600;
}
/* INPUT CLASS -> */
div.input_with_post_text {
display: flex;
align-items: center;
}
div.input_with_post_text span.post_text {
flex: 1;
margin-left: 0.5em;
}
div.input_with_post_text input {
width: 70%;
display: inline-block;
flex: 2;
}
/* <- INPUT CLASS */
dl.chill_report_view_data,
dl.chill_view_data {
dt {
margin-top: 1.5em;
color: $chill-blue;
}
dd {
padding-left: 1.5em;
margin-top: 0.2em;
ul {
padding-left: 0;
}
}
}
blockquote.chill-user-quote,
div.chill-user-quote {
border-left: 10px solid $chill-yellow;
margin: 1.5em 10px;
padding: 0.5em 10px;
quotes: "\201C""\201D""\2018""\2019";
background-color: $chill-llight-gray;
blockquote {
margin: 1.5em 10px;
padding: 0.5em 10px;
}
blockquote:before {
color: #ccc;
content: open-quote;
font-size: 4em;
line-height: 0.1em;
margin-right: 0.25em;
vertical-align: -0.4em;
}
}
.chill-no-data-statement {
font-style: italic;
}

View File

@@ -1,7 +0,0 @@
// Buttons
$button-font-size : 15px !default;
$button-text-color : #000 !default;
$button-text-weight : 300 !default;
$button-padding : 8px 12px !default;
$button-background : #eee !default;
$button-margin : 5px !default;

View File

@@ -1,64 +0,0 @@
// BODY
$body-background : #fff !default;
// PLAIN TEXT
$text-color : #555 !default;
// HEADINGS
$headings-color : #404040 !default;
// LINKS
$link-color : #6998C9 !default;
$link-visited-color : #808080 !default;
$link-active-color : shade(red,5%) !default;
$link-hover-color : #007ED5 !default;
$link-focus-color : $link-color !default;
$white : #fff !default;
$black : #000 !default;
$orange : #FF622C !default;
$red : #C83D3D !default;
$green : #27806f !default;
$blue : #2980b9 !default;
$yellow : #FFC82C !default;
$light-blue : #4995C7;
$llight-blue : #72B0D9;
$dark-blue : #096EB0 !default;
$ddark-blue : #07507F !default;
$light-green : #419484 !default;
$llight-green : #6CB3A5 !default;
$warning-bg : $orange !default;
$caution-bg : $red !default;
$error-bg : $red !default;
$success-bg : $green !default;
$info-bg : $blue !default;
$grey-95 : lighten($black, 5%) !default;
$grey-90 : lighten($black, 10%) !default;
$grey-85 : lighten($black, 15%) !default;
$grey-80 : lighten($black, 20%) !default;
$grey-75 : lighten($black, 25%) !default;
$grey-70 : lighten($black, 30%) !default;
$grey-65 : lighten($black, 35%) !default;
$grey-60 : lighten($black, 40%) !default;
$grey-55 : lighten($black, 45%) !default;
$grey-50 : lighten($black, 50%) !default;
$grey-45 : lighten($black, 55%) !default;
$grey-40 : lighten($black, 60%) !default;
$grey-35 : lighten($black, 65%) !default;
$grey-30 : lighten($black, 70%) !default;
$grey-25 : lighten($black, 75%) !default;
$grey-20 : lighten($black, 80%) !default;
$grey-15 : lighten($black, 85%) !default;
$grey-10 : lighten($black, 90%) !default;
$grey-5 : lighten($black, 95%) !default;
$dark-grey: $grey-80; //#333;
$medium-grey: $grey-50; //#999;
$light-grey: $grey-20; //#DDD;
@import "../custom/config/colors";

View File

@@ -1,115 +0,0 @@
// Typography
//$sans-serif: $helvetica;
//$serif: $georgia;
//$base-font-family: $sans-serif;
//$header-font-family: $base-font-family;
// Font Sizes
$base-font-size: 1em;
$h1-font-size: $base-font-size * 2.25;
$h2-font-size: $base-font-size * 2;
$h3-font-size: $base-font-size * 1.75;
$h4-font-size: $base-font-size * 1.5;
$h5-font-size: $base-font-size * 1.25;
$h6-font-size: $base-font-size;
// Line height
$base-line-height: 1.5;
$header-line-height: 1.25;
// Other Sizes
$base-border-radius: 3px;
$base-spacing: $base-line-height * 1em;
$base-z-index: 0;
// Background Color
$base-background-color: white;
// Font Colors
$base-font-color: $dark-grey;
$base-accent-color: $blue;
// Link Colors
$base-link-color: $base-accent-color;
$hover-link-color: darken($base-accent-color, 15);
$base-button-color: $base-link-color;
$hover-button-color: $hover-link-color;
/*
// Flash Colors
$alert-color: $light-yellow;
$error-color: $light-red;
$notice-color: lighten($base-accent-color, 40);
$success-color: $light-green;
*/
// Border color
$base-border-color: $light-grey;
$base-border: 1px solid $base-border-color;
// Footer
$footer-background: $grey-50;//-blue;// desaturate(darken($base-accent-color, 20), 30);
$footer-color: white;
$footer-link-color: transparentize($footer-color, .6);
$footer-disclaimer-color: transparentize($footer-color, .6);
$footer-vertical-padding: 0;
// Forms
$form-border-size: 1px;
$form-border-color: $base-border-color;
$form-border-color-hover: darken($base-border-color, 10);
$form-border-color-focus: $base-accent-color;
$form-border-radius: $base-border-radius;
$form-box-shadow: inset 0 1px 3px rgba(black,0.06);
$form-box-shadow-focus: $form-box-shadow, 0 0 5px rgba(darken($form-border-color-focus, 5), 0.7);
$form-font-size: $base-font-size;
//$form-font-family: $base-font-family;
// Navigation
$navigation-background: $dark-grey;
$navigation-color: transparentize(white, 0.3);
$navigation-color-hover: white;
$navigation-height: 60px;
$navigation-active-link-color: transparentize(white, 0.5);
$navigation-submenu-padding: 1em;
$navigation-submenu-width: 12em;
$navigation-border-radius: $base-border-radius;
$navigation-first-padding-top: 1em;
$navigation-last-padding-bottom: 0.7em;
$navigation-more-pin: '\25BE';
$navigation-more-pin-color: $navigation-color;
$navigation-ul-submenu-top: 1.5em;
$navigation-ul-submenu-padding-left: 0;
$navigation-search-padding: .85em .6em;
$navigation-border-bottom: 1px solid darken($navigation-background, 10);
//Table
$table-width: 100%;
$table-head-bg-color: $orange;
$table-head-td-border: unset;
$table-head-td-text-align: center;
$table-head-td-padding: 0.3em;
$table-head-text-color: $white;
$table-body-tr-bg-color-even: $grey-5;
$table-body-tr-bg-color-odd: unset;
$table-body-td-border: unset;
$table-body-td-text-align: left;
$table-body-td-padding: 0.3em;
$table-body-text-color: unset;
//Tabs
$tabs-nav-margin-bottom: none;
$tabs-nav-title-bg-color: $blue;
$tabs-nav-title-text-color: $white;
$tabs-nav-title-padding: 0.5em 0.5em 0.5em 1em;
$tabs-nav-bg-color: none;
$tabs-nav-text-color: inherit;
$tabs-new-border: 3px solid transparent;
$tabs-nav-hover-border: 3px solid $orange;
$tabs-nav-hover-text-color: inherit;
$tabs-nav-font-family: unset;
$tabs-nav-padding: 0.5em 0.5em 0.5em 1.5em;
@import "../custom/config/variables";

View File

@@ -1,411 +0,0 @@
// The following features have been deprecated and will be removed in the next MAJOR version release
@mixin inline-block {
display: inline-block;
@warn "The inline-block mixin is deprecated and will be removed in the next major version release";
}
@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) {
@if type-of($style) == string and type-of($base-color) == color {
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == string and type-of($base-color) == number {
$padding: $text-size;
$text-size: $base-color;
$base-color: #4294f0;
@if $padding == inherit {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == color and type-of($base-color) == color {
$base-color: $style;
$style: simple;
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == color and type-of($base-color) == number {
$padding: $text-size;
$text-size: $base-color;
$base-color: $style;
$style: simple;
@if $padding == inherit {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == number {
$padding: $base-color;
$text-size: $style;
$base-color: #4294f0;
$style: simple;
@if $padding == #4294f0 {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
@warn "The button mixin is deprecated and will be removed in the next major version release";
}
// Selector Style Button
@mixin buttonstyle($type, $b-color, $t-size, $pad) {
// Grayscale button
@if $type == simple and $b-color == grayscale($b-color) {
@include simple($b-color, true, $t-size, $pad);
}
@if $type == shiny and $b-color == grayscale($b-color) {
@include shiny($b-color, true, $t-size, $pad);
}
@if $type == pill and $b-color == grayscale($b-color) {
@include pill($b-color, true, $t-size, $pad);
}
@if $type == flat and $b-color == grayscale($b-color) {
@include flat($b-color, true, $t-size, $pad);
}
// Colored button
@if $type == simple {
@include simple($b-color, false, $t-size, $pad);
}
@else if $type == shiny {
@include shiny($b-color, false, $t-size, $pad);
}
@else if $type == pill {
@include pill($b-color, false, $t-size, $pad);
}
@else if $type == flat {
@include flat($b-color, false, $t-size, $pad);
}
}
// Simple Button
@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
$inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);
$stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);
$text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border: grayscale($border);
$inset-shadow: grayscale($inset-shadow);
$stop-gradient: grayscale($stop-gradient);
$text-shadow: grayscale($text-shadow);
}
border: 1px solid $border;
border-radius: 3px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
@include linear-gradient ($base-color, $stop-gradient);
padding: $padding;
text-decoration: none;
text-shadow: 0 1px 0 $text-shadow;
background-clip: padding-box;
&:hover:not(:disabled) {
$base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
$inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);
$stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
$inset-shadow-hover: grayscale($inset-shadow-hover);
$stop-gradient-hover: grayscale($stop-gradient-hover);
}
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
$inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);
@if $grayscale == true {
$border-active: grayscale($border-active);
$inset-shadow-active: grayscale($inset-shadow-active);
}
border: 1px solid $border-active;
box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active;
}
}
// Shiny Button
@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);
$border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);
$fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);
$inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);
$second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);
$text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);
$third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border: grayscale($border);
$border-bottom: grayscale($border-bottom);
$fourth-stop: grayscale($fourth-stop);
$inset-shadow: grayscale($inset-shadow);
$second-stop: grayscale($second-stop);
$text-shadow: grayscale($text-shadow);
$third-stop: grayscale($third-stop);
}
@include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);
border: 1px solid $border;
border-bottom: 1px solid $border-bottom;
border-radius: 5px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
padding: $padding;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 1px $text-shadow;
&:hover:not(:disabled) {
$first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);
$second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);
$third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);
$fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);
@if $grayscale == true {
$first-stop-hover: grayscale($first-stop-hover);
$second-stop-hover: grayscale($second-stop-hover);
$third-stop-hover: grayscale($third-stop-hover);
$fourth-stop-hover: grayscale($fourth-stop-hover);
}
@include linear-gradient(top, $first-stop-hover 0%,
$second-stop-hover 50%,
$third-stop-hover 50%,
$fourth-stop-hover 100%);
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);
@if $grayscale == true {
$inset-shadow-active: grayscale($inset-shadow-active);
}
box-shadow: inset 0 0 20px 0 $inset-shadow-active;
}
}
// Pill Button
@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);
$border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);
$inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);
$stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);
$text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border-bottom: grayscale($border-bottom);
$border-sides: grayscale($border-sides);
$border-top: grayscale($border-top);
$inset-shadow: grayscale($inset-shadow);
$stop-gradient: grayscale($stop-gradient);
$text-shadow: grayscale($text-shadow);
}
border: 1px solid $border-top;
border-color: $border-top $border-sides $border-bottom;
border-radius: 16px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: normal;
line-height: 1;
@include linear-gradient ($base-color, $stop-gradient);
padding: $padding;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 1px $text-shadow;
background-clip: padding-box;
&:hover:not(:disabled) {
$base-color-hover: adjust-color($base-color, $lightness: -4.5%);
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);
$border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);
$inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);
$stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);
$text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
$border-bottom: grayscale($border-bottom);
$border-sides: grayscale($border-sides);
$border-top: grayscale($border-top);
$inset-shadow-hover: grayscale($inset-shadow-hover);
$stop-gradient-hover: grayscale($stop-gradient-hover);
$text-shadow-hover: grayscale($text-shadow-hover);
}
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
background-clip: padding-box;
border: 1px solid $border-top;
border-color: $border-top $border-sides $border-bottom;
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
cursor: pointer;
text-shadow: 0 -1px 1px $text-shadow-hover;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);
$border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);
$border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);
$inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);
$text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);
@if $grayscale == true {
$active-color: grayscale($active-color);
$border-active: grayscale($border-active);
$border-bottom-active: grayscale($border-bottom-active);
$inset-shadow-active: grayscale($inset-shadow-active);
$text-shadow-active: grayscale($text-shadow-active);
}
background: $active-color;
border: 1px solid $border-active;
border-bottom: 1px solid $border-bottom-active;
box-shadow: inset 0 0 6px 3px $inset-shadow-active;
text-shadow: 0 -1px 1px $text-shadow-active;
}
}
// Flat Button
@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
}
background-color: $base-color;
border-radius: 3px;
border: 0;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
padding: $padding;
text-decoration: none;
background-clip: padding-box;
&:hover:not(:disabled){
$base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
}
background-color: $base-color-hover;
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
@if $grayscale == true {
$base-color-active: grayscale($base-color-active);
}
background-color: $base-color-active;
cursor: pointer;
}
}
// Flexible grid
@function flex-grid($columns, $container-columns: $fg-max-columns) {
$width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($width / $container-width);
@warn "The flex-grid function is deprecated and will be removed in the next major version release";
}
// Flexible gutter
@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($gutter / $container-width);
@warn "The flex-gutter function is deprecated and will be removed in the next major version release";
}
@function grid-width($n) {
@return $n * $gw-column + ($n - 1) * $gw-gutter;
@warn "The grid-width function is deprecated and will be removed in the next major version release";
}
@function golden-ratio($value, $increment) {
@return modular-scale($increment, $value, $ratio: $golden);
@warn "The golden-ratio function is deprecated and will be removed in the next major version release. Please use the modular-scale function, instead.";
}
@mixin box-sizing($box) {
@include prefixer(box-sizing, $box, webkit moz spec);
@warn "The box-sizing mixin is deprecated and will be removed in the next major version release. This property can now be used un-prefixed.";
}

View File

@@ -1,87 +0,0 @@
// Bourbon 4.2.6
// http://bourbon.io
// Copyright 2011-2015 thoughtbot, inc.
// MIT License
@import "settings/prefixer";
@import "settings/px-to-em";
@import "settings/asset-pipeline";
@import "functions/assign-inputs";
@import "functions/contains";
@import "functions/contains-falsy";
@import "functions/is-length";
@import "functions/is-light";
@import "functions/is-number";
@import "functions/is-size";
@import "functions/px-to-em";
@import "functions/px-to-rem";
@import "functions/shade";
@import "functions/strip-units";
@import "functions/tint";
@import "functions/transition-property-name";
@import "functions/unpack";
@import "functions/modular-scale";
@import "helpers/convert-units";
@import "helpers/directional-values";
@import "helpers/font-source-declaration";
@import "helpers/gradient-positions-parser";
@import "helpers/linear-angle-parser";
@import "helpers/linear-gradient-parser";
@import "helpers/linear-positions-parser";
@import "helpers/linear-side-corner-parser";
@import "helpers/radial-arg-parser";
@import "helpers/radial-positions-parser";
@import "helpers/radial-gradient-parser";
@import "helpers/render-gradients";
@import "helpers/shape-size-stripper";
@import "helpers/str-to-num";
@import "css3/animation";
@import "css3/appearance";
@import "css3/backface-visibility";
@import "css3/background";
@import "css3/background-image";
@import "css3/border-image";
@import "css3/calc";
@import "css3/columns";
@import "css3/filter";
@import "css3/flex-box";
@import "css3/font-face";
@import "css3/font-feature-settings";
@import "css3/hidpi-media-query";
@import "css3/hyphens";
@import "css3/image-rendering";
@import "css3/keyframes";
@import "css3/linear-gradient";
@import "css3/perspective";
@import "css3/placeholder";
@import "css3/radial-gradient";
@import "css3/selection";
@import "css3/text-decoration";
@import "css3/transform";
@import "css3/transition";
@import "css3/user-select";
@import "addons/border-color";
@import "addons/border-radius";
@import "addons/border-style";
@import "addons/border-width";
@import "addons/buttons";
@import "addons/clearfix";
@import "addons/ellipsis";
@import "addons/font-stacks";
@import "addons/hide-text";
@import "addons/margin";
@import "addons/padding";
@import "addons/position";
@import "addons/prefixer";
@import "addons/retina-image";
@import "addons/size";
@import "addons/text-inputs";
@import "addons/timing-functions";
@import "addons/triangle";
@import "addons/word-wrap";
@import "bourbon-deprecated-upcoming";

View File

@@ -1,26 +0,0 @@
@charset "UTF-8";
/// Provides a quick method for targeting `border-color` on specific sides of a box. Use a `null` value to “skip” a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-color(#a60b55 #76cd9c null #e8ae1a);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-left-color: #e8ae1a;
/// border-right-color: #76cd9c;
/// border-top-color: #a60b55;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-color`
@mixin border-color($vals...) {
@include directional-property(border, color, $vals...);
}

View File

@@ -1,48 +0,0 @@
@charset "UTF-8";
/// Provides a quick method for targeting `border-radius` on both corners on the side of a box.
///
/// @param {Number} $radii
/// List of arguments
///
/// @example scss - Usage
/// .element-one {
/// @include border-top-radius(5px);
/// }
///
/// .element-two {
/// @include border-left-radius(3px);
/// }
///
/// @example css - CSS Output
/// .element-one {
/// border-top-left-radius: 5px;
/// border-top-right-radius: 5px;
/// }
///
/// .element-two {
/// border-bottom-left-radius: 3px;
/// border-top-left-radius: 3px;
/// }
///
/// @output `border-radius`
@mixin border-top-radius($radii) {
border-top-left-radius: $radii;
border-top-right-radius: $radii;
}
@mixin border-right-radius($radii) {
border-bottom-right-radius: $radii;
border-top-right-radius: $radii;
}
@mixin border-bottom-radius($radii) {
border-bottom-left-radius: $radii;
border-bottom-right-radius: $radii;
}
@mixin border-left-radius($radii) {
border-bottom-left-radius: $radii;
border-top-left-radius: $radii;
}

View File

@@ -1,25 +0,0 @@
@charset "UTF-8";
/// Provides a quick method for targeting `border-style` on specific sides of a box. Use a `null` value to “skip” a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-style(dashed null solid);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-bottom-style: solid;
/// border-top-style: dashed;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-style`
@mixin border-style($vals...) {
@include directional-property(border, style, $vals...);
}

View File

@@ -1,25 +0,0 @@
@charset "UTF-8";
/// Provides a quick method for targeting `border-width` on specific sides of a box. Use a `null` value to “skip” a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-width(1em null 20px);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-bottom-width: 20px;
/// border-top-width: 1em;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-width`
@mixin border-width($vals...) {
@include directional-property(border, width, $vals...);
}

View File

@@ -1,64 +0,0 @@
@charset "UTF-8";
/// Generates variables for all buttons. Please note that you must use interpolation on the variable: `#{$all-buttons}`.
///
/// @example scss - Usage
/// #{$all-buttons} {
/// background-color: #f00;
/// }
///
/// #{$all-buttons-focus},
/// #{$all-buttons-hover} {
/// background-color: #0f0;
/// }
///
/// #{$all-buttons-active} {
/// background-color: #00f;
/// }
///
/// @example css - CSS Output
/// button,
/// input[type="button"],
/// input[type="reset"],
/// input[type="submit"] {
/// background-color: #f00;
/// }
///
/// button:focus,
/// input[type="button"]:focus,
/// input[type="reset"]:focus,
/// input[type="submit"]:focus,
/// button:hover,
/// input[type="button"]:hover,
/// input[type="reset"]:hover,
/// input[type="submit"]:hover {
/// background-color: #0f0;
/// }
///
/// button:active,
/// input[type="button"]:active,
/// input[type="reset"]:active,
/// input[type="submit"]:active {
/// background-color: #00f;
/// }
///
/// @require assign-inputs
///
/// @type List
///
/// @todo Remove double assigned variables (Lines 5962) in v5.0.0
$buttons-list: 'button',
'input[type="button"]',
'input[type="reset"]',
'input[type="submit"]';
$all-buttons: assign-inputs($buttons-list);
$all-buttons-active: assign-inputs($buttons-list, active);
$all-buttons-focus: assign-inputs($buttons-list, focus);
$all-buttons-hover: assign-inputs($buttons-list, hover);
$all-button-inputs: $all-buttons;
$all-button-inputs-active: $all-buttons-active;
$all-button-inputs-focus: $all-buttons-focus;
$all-button-inputs-hover: $all-buttons-hover;

View File

@@ -1,25 +0,0 @@
@charset "UTF-8";
/// Provides an easy way to include a clearfix for containing floats.
///
/// @link http://cssmojo.com/latest_new_clearfix_so_far/
///
/// @example scss - Usage
/// .element {
/// @include clearfix;
/// }
///
/// @example css - CSS Output
/// .element::after {
/// clear: both;
/// content: "";
/// display: table;
/// }
@mixin clearfix {
&::after {
clear: both;
content: "";
display: table;
}
}

View File

@@ -1,30 +0,0 @@
@charset "UTF-8";
/// Truncates text and adds an ellipsis to represent overflow.
///
/// @param {Number} $width [100%]
/// Max-width for the string to respect before being truncated
///
/// @example scss - Usage
/// .element {
/// @include ellipsis;
/// }
///
/// @example css - CSS Output
/// .element {
/// display: inline-block;
/// max-width: 100%;
/// overflow: hidden;
/// text-overflow: ellipsis;
/// white-space: nowrap;
/// word-wrap: normal;
/// }
@mixin ellipsis($width: 100%) {
display: inline-block;
max-width: $width;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
}

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