Merge remote-tracking branch 'origin/master' into feat/add-document-encrypter-service

This commit is contained in:
Julien Fastré 2022-04-26 09:37:30 +02:00
commit e72a2004d7
329 changed files with 5953 additions and 2055 deletions

View File

@ -11,6 +11,77 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [Datepickers] datepickers fixed when using keyboard to enter date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/545)
* [social_action] Display 'agents traitants' in parcours resumé and social action list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/568)
* [Person_search] Closed parcours shown within an accordeon that can be opened/closed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/574)
## Test releases
### 2021-04-24
* [notification email on course designation] allow raw string in email content generation
* [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older
* [Documents] Change wording 'créer' to 'enregistrer' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/634)
* [Parcours]: The number of 'mes parcours' displayed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/572)
* [Hompage_widget]: Renaming of tabs and removal of social actions tab (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/570)
* [activity]: Ignore thirdparties when creating a social action via an activity (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/573)
* [parcours]: change wording of warning message and button when user is not associated to a household yet (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/590#note_918370943)
* [Accompanying period work evaluations] list documents associated to a work by creation date, and then by id, from the most recent to older
* [Course comment] add validationConstraint NotNull and NotBlank on comment content, to avoid sql error
* [Notifications] delay the sending of notificaiton to kernel.terminate
* [Notifications / Period user change] fix the sending of notification when user changes
* [Activity form] invert 'incoming' and 'receiving' in Activity form
* [Activity form] keep the same order for 'attendee' field in new and edit form
* [list with period] use "sameas" test operator to introduce requestor in list
* [notification email on course designation] allow raw string in email content generation
* [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older
### 2021-04-13
* [person] household address: add a form for editing the validFrom date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/541)
* [person] householdmemberseditor: fix composition type bug in select form (vuejs) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/543)
* [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person);
* [docgen] add a new context with a list of activities in course
* [docgen] add a comment in budget lines
* [notifications] allow to send a notification to an email address. The address receive an access link
* [adresses] add constraints in database to avoid errors later: postcode not null, and validfrom <= validto
* [accompanying work editor] add a label on document title input
### 2021-04-07
* notification list: move action buttons outside of the toggle
* fix detecting of non-read notification
* filter users which are disabled in search user api
* order query for location and add pagination in list
* allow every person which has part for a workflow to see the workflow page
* able to see the workflow if the evaluation document has been deleted
* hardcode the list of supported mime types for edition with collabora
* list of accompanying course: allow to see the pinned comment in list_item
### 2021-04-06
* [main] notification toggle read: correct js syntax for compilation in production (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/548)
* [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535)
* [activity] espace entre les boutons pour supprimer les documents
### continuous release in February and March
* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType
* [person] Accompanying course evaluation documents: disable the WOPI edit link if mimetype not supported and if no keyInfos
(https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/585)
* [activity] display error messages above the form in creating a new location (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/481)
* [activity] show required field in activity edit/new by an asterix in the vuejs fields (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/494)
* [ACL] fix allow to see the course, event if the scope'course does not contains the scope's user
* [search] enforce limit of results for fetching rsults by search api https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/576
* [activity] Fix delete button for document (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/554)
* [activity] Add return path the document generation (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/553)
* [person] add person ressource to person docgen normaliser (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/517)
* [person] AccompanyingCourseWorkEdit: fix deleting evaluation documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/546)
* [person] AccompanyingCourseWorkEdit: download existing documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/512)
* [person] AccompanyingCourseWorkEdit: replace document by a new one (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/511)
* [person] AccompanyingPeriodWork: add referrers to work, add doctrine event listener to add logged user to referrers collection and display a referrers list in work list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/502)
* [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495) * [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495)
* [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493) * [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493)
* [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477) * [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477)
@ -27,7 +98,8 @@ and this project adheres to
* [Person/Household list] when listing other simultaneous members of an household, exclude the members on person, not on members (avoid to show two membersship with the same person) * [Person/Household list] when listing other simultaneous members of an household, exclude the members on person, not on members (avoid to show two membersship with the same person)
* [draft periods] add a delete button (if acl granted) on each draft period listed on draft period page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/463) * [draft periods] add a delete button (if acl granted) on each draft period listed on draft period page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/463)
* [Person] Display suffixText in RenderPerson, PersonText.vue, RenderPersonBox.vue (was made for displaying "enfant confie") (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/441) * [Person] Display suffixText in RenderPerson, PersonText.vue, RenderPersonBox.vue (was made for displaying "enfant confie") (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/441)
* [person] residential address: show residential address or info in PersonRenderBox, refactor Residential Address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/439) * [budget]: budget enabled for persons and households (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/469)
* [person] residential address: show residential address or info in PersonRenderBox, refactor Residential Address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/439)
* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345) * [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
* [documents] Improve flex-table item-col placement when long buttons and long metadata * [documents] Improve flex-table item-col placement when long buttons and long metadata
* [thirdparty] Fix display of multiple contact badges so they wrap onto next line (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/482) * [thirdparty] Fix display of multiple contact badges so they wrap onto next line (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/482)
@ -44,10 +116,25 @@ and this project adheres to
* [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496) * [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496)
* [person_resource] separate create page created to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/504) * [person_resource] separate create page created to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/504)
* [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506) * [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506)
* [thirdparty] For contacts show current civility/profession in edit form + fix saving of edited information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/491)
* [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505) * [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505)
* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486) * [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486)
* [thirdparty] add firstname field to thirdparty 'child' or 'contact' types (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/508)
## Test releases * [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505)
* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486)
* [parcours] List of parcours for a specific user so they can be reassigned in case of absence (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/509)
* [thirdparty] Thirdparty view page, english text translated (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/534)
* [social_action] Translation changed in evaluation section (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/512)
* [filiation] Possible to add person (or create onthefly) to add to filiation graph + add relation (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/519)
* [household] Within parcours listing page of household add create button (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/560)
* [person_resource] bugfix when adding thirdparty or freetext resource + prevent personOwner themselves to be added. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/526)
* [aside_activity] style correction + sticky-form create button (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/529)
* [budget] order within the menu adjusted (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/592)
* [onthefly] fix create person. Bug was noticed in filiation (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/591)
* [parcours] Create document buttons made sticky (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/532)
* [person] Trailing guillemet removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/530)
* [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537)
* [onthefly] trim trailing whitespace in email of person and thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/542)
### test release 2022-02-21 ### test release 2022-02-21
@ -75,8 +162,6 @@ and this project adheres to
* [bug]: fix confidential toggle of address in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/460) * [bug]: fix confidential toggle of address in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/460)
## Test releases
* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType
### test release 2022-02-14 ### test release 2022-02-14
@ -103,6 +188,7 @@ and this project adheres to
* [address]: Correction residential address 'depuis le' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/459) * [address]: Correction residential address 'depuis le' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/459)
* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414) * [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414)
* [Thirdparty_contact]: address blurred if confidential in view page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/450) * [Thirdparty_contact]: address blurred if confidential in view page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/450)
* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
### test release 2021-02-01 ### test release 2021-02-01
@ -125,18 +211,9 @@ and this project adheres to
* [fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413 * [fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413
* [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc. * [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345) * [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
## Test releases
=======
* [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc. * [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
>>>>>>> issue422_and_others_on_AddPersons
=======
* [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
>>>>>>> b0d50d315c8e00959a967badac9cf5057ab2b4bc
### test release 2021-01-31 ### test release 2021-01-31
* [person] accompanying course: optimisation: do not fetch some resources for the banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/409) * [person] accompanying course: optimisation: do not fetch some resources for the banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/409)

View File

@ -32,7 +32,7 @@ Then, render the pagination using the dedicated twig function.
{% block title 'Item list'|trans %} {% block title 'Item list'|trans %}
{% block personcontent %} {% block content %}
<table> <table>

View File

@ -156,7 +156,7 @@ This layout extend `ChillMainBundle::layoutWithVerticalMenu.html.twig` add the p
It proposes 1 new block : It proposes 1 new block :
* personcontent * content
* where to display the information of the person * where to display the information of the person

View File

@ -222,33 +222,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php path: src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php
-
message:
"""
#^Class Chill\\\\AMLI\\\\BudgetBundle\\\\Controller\\\\AbstractElementController extends deprecated class Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\Controller\\:
since Symfony 4\\.2, use "Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\AbstractController" instead\\.$#
"""
count: 1
path: src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php
-
message:
"""
#^Class Chill\\\\AMLI\\\\BudgetBundle\\\\Controller\\\\ElementController extends deprecated class Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\Controller\\:
since Symfony 4\\.2, use "Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\AbstractController" instead\\.$#
"""
count: 1
path: src/Bundle/ChillBudgetBundle/Controller/ElementController.php
-
message:
"""
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php
- -
message: message:
""" """

View File

@ -250,11 +250,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillMainBundle/Entity/Address.php path: src/Bundle/ChillMainBundle/Entity/Address.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php
- -
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1 count: 1
@ -455,18 +450,8 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php
- -
message: "#^Method Chill\\\\ThirdPartyBundle\\\\Search\\\\ThirdPartySearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#" message: "#^Method Chill\\\\ThirdPartyBundle\\\\Search\\\\ThirdPartySearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#"
count: 1 count: 1
path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartySearch.php path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartySearch.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php

View File

@ -179,14 +179,15 @@ final class ActivityController extends AbstractController
{ {
$view = null; $view = null;
[$person, $accompanyingPeriod] = $this->getEntity($request);
$entity = $this->activityRepository->find($id); $entity = $this->activityRepository->find($id);
if (null === $entity) { if (null === $entity) {
throw $this->createNotFoundException('Unable to find Activity entity.'); throw $this->createNotFoundException('Unable to find Activity entity.');
} }
$accompanyingPeriod = $entity->getAccompanyingPeriod();
$person = $entity->getPerson();
if ($entity->getAccompanyingPeriod() instanceof AccompanyingPeriod) { if ($entity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:editAccompanyingCourse.html.twig'; $view = 'ChillActivityBundle:Activity:editAccompanyingCourse.html.twig';
$accompanyingPeriod = $entity->getAccompanyingPeriod(); $accompanyingPeriod = $entity->getAccompanyingPeriod();
@ -220,6 +221,9 @@ final class ActivityController extends AbstractController
$this->entityManager->persist($entity); $this->entityManager->persist($entity);
$this->entityManager->flush(); $this->entityManager->flush();
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
$params['id'] = $entity->getId();
if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) { if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) {
return $this->redirectToRoute( return $this->redirectToRoute(
'chill_docgenerator_generate_from_template', 'chill_docgenerator_generate_from_template',
@ -227,15 +231,13 @@ final class ActivityController extends AbstractController
'template' => $form->get('gendocTemplateId')->getData(), 'template' => $form->get('gendocTemplateId')->getData(),
'entityClassName' => Activity::class, 'entityClassName' => Activity::class,
'entityId' => $entity->getId(), 'entityId' => $entity->getId(),
'returnPath' => $this->generateUrl('chill_activity_activity_edit', $params),
] ]
); );
} }
$this->addFlash('success', $this->get('translator')->trans('Success : activity updated!')); $this->addFlash('success', $this->get('translator')->trans('Success : activity updated!'));
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
$params['id'] = $entity->getId();
return $this->redirectToRoute('chill_activity_activity_show', $params); return $this->redirectToRoute('chill_activity_activity_show', $params);
} }
@ -444,6 +446,9 @@ final class ActivityController extends AbstractController
'template' => $form->get('gendocTemplateId')->getData(), 'template' => $form->get('gendocTemplateId')->getData(),
'entityClassName' => Activity::class, 'entityClassName' => Activity::class,
'entityId' => $entity->getId(), 'entityId' => $entity->getId(),
'returnPath' => $this->generateUrl('chill_activity_activity_edit', [
'id' => $entity->getId(),
]),
] ]
); );
} }

View File

@ -66,9 +66,6 @@ class ActivityEntityListener
$newAction->addPerson($person); $newAction->addPerson($person);
} }
foreach ($associatedThirdparties as $thirdparty) {
$newAction->setHandlingThierparty($thirdparty);
}
$this->em->persist($newAction); $this->em->persist($newAction);
$this->em->flush(); $this->em->flush();
} }

View File

@ -125,7 +125,9 @@ class ActivityType extends AbstractType
} }
if ($activityType->isVisible('socialIssues') && $accompanyingPeriod) { if ($activityType->isVisible('socialIssues') && $accompanyingPeriod) {
$builder->add('socialIssues', HiddenType::class); $builder->add('socialIssues', HiddenType::class, [
'required' => $activityType->getSocialIssuesVisible() === 2,
]);
$builder->get('socialIssues') $builder->get('socialIssues')
->addModelTransformer(new CallbackTransformer( ->addModelTransformer(new CallbackTransformer(
static function (iterable $socialIssuesAsIterable): string { static function (iterable $socialIssuesAsIterable): string {
@ -151,7 +153,9 @@ class ActivityType extends AbstractType
} }
if ($activityType->isVisible('socialActions') && $accompanyingPeriod) { if ($activityType->isVisible('socialActions') && $accompanyingPeriod) {
$builder->add('socialActions', HiddenType::class); $builder->add('socialActions', HiddenType::class, [
'required' => $activityType->getSocialActionsVisible() === 2,
]);
$builder->get('socialActions') $builder->get('socialActions')
->addModelTransformer(new CallbackTransformer( ->addModelTransformer(new CallbackTransformer(
static function (iterable $socialActionsAsIterable): string { static function (iterable $socialActionsAsIterable): string {
@ -305,6 +309,7 @@ class ActivityType extends AbstractType
'label' => $activityType->getLabel('documents'), 'label' => $activityType->getLabel('documents'),
'required' => $activityType->isRequired('documents'), 'required' => $activityType->isRequired('documents'),
'allow_add' => true, 'allow_add' => true,
'allow_delete' => true,
'button_add_label' => 'activity.Insert a document', 'button_add_label' => 'activity.Insert a document',
'button_remove_label' => 'activity.Remove a document', 'button_remove_label' => 'activity.Remove a document',
'empty_collection_explain' => 'No documents', 'empty_collection_explain' => 'No documents',
@ -338,7 +343,9 @@ class ActivityType extends AbstractType
} }
if ($activityType->isVisible('location')) { if ($activityType->isVisible('location')) {
$builder->add('location', HiddenType::class) $builder->add('location', HiddenType::class, [
'required' => $activityType->getLocationVisible() === 2,
])
->get('location') ->get('location')
->addModelTransformer(new CallbackTransformer( ->addModelTransformer(new CallbackTransformer(
static function (?Location $location): string { static function (?Location $location): string {
@ -366,8 +373,8 @@ class ActivityType extends AbstractType
'label' => $activityType->getLabel('sentReceived'), 'label' => $activityType->getLabel('sentReceived'),
'required' => $activityType->isRequired('sentReceived'), 'required' => $activityType->isRequired('sentReceived'),
'choices' => [ 'choices' => [
'Sent' => Activity::SENTRECEIVED_SENT,
'Received' => Activity::SENTRECEIVED_RECEIVED, 'Received' => Activity::SENTRECEIVED_RECEIVED,
'Sent' => Activity::SENTRECEIVED_SENT,
], ],
]); ]);
} }

View File

@ -12,13 +12,20 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Repository; namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@ -72,6 +79,86 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy); ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy);
} }
public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array
{
$rsm = new ResultSetMappingBuilder($this->em);
$sql = '
SELECT
a.id AS activity_id,
date,
CASE WHEN durationtime IS NOT NULL THEN (EXTRACT(EPOCH from durationtime) / 60)::int ELSE 0 END AS durationtimeminute,
attendee_id,
comment_comment,
emergency,
sentreceived,
CASE WHEN traveltime IS NOT NULL THEN (EXTRACT(EPOCH from traveltime) / 60)::int ELSE 0 END AS traveltimeminute,
t.id AS type_id, t.name as type_name,
p.id AS presence_id, p.name AS presence_name,
location.id AS location_id, location.address_id, location.name AS location_name, location.phonenumber1, location.phonenumber2, location.email,
location.locationtype_id, locationtype.title AS locationtype_title,
users.userids AS userids,
thirdparties.thirdpartyids,
persons.personids,
actions.socialactionids,
issues.socialissueids
FROM activity a
LEFT JOIN chill_main_location location ON a.location_id = location.id
LEFT JOIN chill_main_location_type locationtype ON location.locationtype_id = locationtype.id
LEFT JOIN activitytpresence p ON a.attendee_id = p.id
LEFT JOIN activitytype t ON a.type_id = t.id
LEFT JOIN LATERAL (SELECT jsonb_agg(user_id) userids, activity_id FROM activity_user AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS users ON TRUE
LEFT JOIN LATERAL (SELECT jsonb_agg(thirdparty_id) thirdpartyids, activity_id FROM activity_thirdparty AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS thirdparties ON TRUE
LEFT JOIN LATERAL (SELECT jsonb_agg(person_id) personids, activity_id FROM activity_person AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS persons ON TRUE
LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE
LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE
WHERE accompanyingperiod_id = ?
ORDER BY a.date DESC, a.id DESC
LIMIT ?
';
$rsm
->addEntityResult(Activity::class, 'a')
->addFieldResult('a', 'activity_id', 'id')
->addFieldResult('a', 'date', 'date')
->addFieldResult('a', 'comment', 'comment')
->addFieldResult('a', 'sentreceived', 'sentReceived')
->addFieldResult('a', 'emergency', 'emergency')
->addJoinedEntityResult(Location::class, 'location', 'a', 'location')
->addFieldResult('location', 'location_id', 'id')
->addFieldResult('location', 'location_name', 'name')
->addFieldResult('location', 'phonenumber1', 'phonenumber1')
->addFieldResult('location', 'phonenumber2', 'phonenumber2')
->addFieldResult('location', 'email', 'email')
->addJoinedEntityResult(LocationType::class, 'locationType', 'location', 'locationType')
->addFieldResult('locationType', 'locationtype_id', 'id')
->addFieldResult('locationType', 'locationtype_title', 'title')
->addJoinedEntityResult(ActivityType::class, 'activityType', 'a', 'activityType')
->addFieldResult('activityType', 'type_id', 'id')
->addFieldResult('activityType', 'type_name', 'name')
->addJoinedEntityResult(ActivityPresence::class, 'activityPresence', 'a', 'attendee')
->addFieldResult('activityPresence', 'presence_id', 'id')
->addFieldResult('activityPresence', 'presence_name', 'name')
// results which cannot be mapped into entity
->addScalarResult('comment_comment', 'comment', Types::TEXT)
->addScalarResult('userids', 'userIds', Types::JSON)
->addScalarResult('thirdpartyids', 'thirdPartyIds', Types::JSON)
->addScalarResult('personids', 'personIds', Types::JSON)
->addScalarResult('socialactionids', 'socialActionIds', Types::JSON)
->addScalarResult('socialissueids', 'socialIssueIds', Types::JSON)
->addScalarResult('durationtimeminute', 'durationTimeMinute', Types::INTEGER)
->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER);
$nq = $this->em->createNativeQuery($sql, $rsm);
$nq->setParameter(0, $period->getId())->setParameter(1, $limit);
return $nq->getResult(AbstractQuery::HYDRATE_ARRAY);
}
/** /**
* @param array $orderBy * @param array $orderBy
* *

View File

@ -21,6 +21,15 @@ interface ActivityACLAwareRepositoryInterface
*/ */
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
/**
* Return a list of activities, simplified as array (not object).
*
* The aim of this method is to get a long list of activities and keep performance.
*
* @return array an array of array, each item representing an activity
*/
public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array;
/** /**
* @return Activity[]|array * @return Activity[]|array
*/ */

View File

@ -0,0 +1,23 @@
div.chill-dropzone__below-zone {
a.btn-delete {
display: none;
}
}
ul[data-collection-name="documents"] {
button.remove-entry {
margin: 0.5rem 0!important;
}
}
// do it in js does not work
// document.addEventListener('DOMContentLoaded', e => {
// const dropzoneBelow = document.querySelectorAll('div.chill-dropzone__below-zone');
// dropzoneBelow.forEach(
// d => {
// const a = d.querySelector('a.btn-delete');
// d.removeChild(a);
// }
// )
// });

View File

@ -1,7 +1,7 @@
<template> <template>
<teleport to="#location"> <teleport to="#location">
<div class="mb-3 row"> <div class="mb-3 row">
<label class="col-form-label col-sm-4"> <label :class="locationClassList">
{{ $t("activity.location") }} {{ $t("activity.location") }}
</label> </label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -41,6 +41,12 @@ export default {
NewLocation, NewLocation,
VueMultiselect, VueMultiselect,
}, },
data() {
return {
locationClassList:
`col-form-label col-sm-4 ${document.querySelector('input#chill_activitybundle_activity_location').getAttribute('required') ? 'required' : ''}`,
}
},
computed: { computed: {
...mapState(["activity", "availableLocations"]), ...mapState(["activity", "availableLocations"]),
...mapGetters(["suggestedEntities"]), ...mapGetters(["suggestedEntities"]),

View File

@ -18,6 +18,12 @@
</template> </template>
<template v-slot:body> <template v-slot:body>
<form> <form>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<select class="form-select form-select-lg" id="type" required v-model="selectType"> <select class="form-select form-select-lg" id="type" required v-model="selectType">
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option> <option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
@ -54,11 +60,6 @@
<label for="email">{{ $t('activity.location_fields.email') }}</label> <label for="email">{{ $t('activity.location_fields.email') }}</label>
</div> </div>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
</form> </form>
</template> </template>
<template v-slot:footer> <template v-slot:footer>

View File

@ -3,7 +3,7 @@
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-4"> <div class="col-4">
<label class="col-form-label">{{ $t('activity.social_issues') }}</label> <label :class="socialIssuesClassList">{{ $t('activity.social_issues') }}</label>
</div> </div>
<div class="col-8"> <div class="col-8">
@ -42,7 +42,7 @@
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-4"> <div class="col-4">
<label class="col-form-label">{{ $t('activity.social_actions') }}</label> <label :class="socialActionsClassList">{{ $t('activity.social_actions') }}</label>
</div> </div>
<div class="col-8"> <div class="col-8">
@ -54,19 +54,19 @@
{{ $t('activity.select_first_a_social_issue') }} {{ $t('activity.select_first_a_social_issue') }}
</span> </span>
<template v-else-if="socialActionsList.length > 0"> <template v-else-if="socialActionsList.length > 0">
<check-social-action <check-social-action
v-if="socialIssuesSelected.length || socialActionsSelected.length" v-if="socialIssuesSelected.length || socialActionsSelected.length"
v-for="action in socialActionsList" v-for="action in socialActionsList"
:key="action.id" :key="action.id"
:action="action" :action="action"
:selection="socialActionsSelected" :selection="socialActionsSelected"
@updateSelected="updateActionsSelected"> @updateSelected="updateActionsSelected">
</check-social-action> </check-social-action>
</template> </template>
<span v-else-if="actionAreLoaded && socialActionsList.length === 0" class="inline-choice chill-no-data-statement mt-3"> <span v-else-if="actionAreLoaded && socialActionsList.length === 0" class="inline-choice chill-no-data-statement mt-3">
{{ $t('activity.social_action_list_empty') }} {{ $t('activity.social_action_list_empty') }}
</span> </span>
@ -94,6 +94,10 @@ export default {
issueIsLoading: false, issueIsLoading: false,
actionIsLoading: false, actionIsLoading: false,
actionAreLoaded: false, actionAreLoaded: false,
socialIssuesClassList:
`col-form-label ${document.querySelector('input#chill_activitybundle_activity_socialIssues').getAttribute('required') ? 'required' : ''}`,
socialActionsClassList:
`col-form-label ${document.querySelector('input#chill_activitybundle_activity_socialActions').getAttribute('required') ? 'required' : ''}`,
} }
}, },
computed: { computed: {
@ -107,7 +111,7 @@ export default {
return this.$store.state.socialIssuesOther; return this.$store.state.socialIssuesOther;
}, },
socialActionsList() { socialActionsList() {
return this.$store.state.socialActionsList; return this.$store.getters.socialActionsListSorted;
}, },
socialActionsSelected() { socialActionsSelected() {
return this.$store.state.activity.socialActions; return this.$store.state.activity.socialActions;

View File

@ -101,6 +101,9 @@ const store = createStore({
state.activity.activityType.thirdPartiesVisible !== 0) state.activity.activityType.thirdPartiesVisible !== 0)
); );
}, },
socialActionsListSorted(state) {
return [ ...state.socialActionsList].sort((a, b) => a.ordering - b.ordering);
},
}, },
mutations: { mutations: {
// SocialIssueAcc // SocialIssueAcc
@ -131,7 +134,6 @@ const store = createStore({
state.socialActionsList = []; state.socialActionsList = [];
}, },
addActionInList(state, action) { addActionInList(state, action) {
//console.log('add action list', action.id);
state.socialActionsList.push(action); state.socialActionsList.push(action);
}, },
updateActionsSelected(state, actions) { updateActionsSelected(state, actions) {

View File

@ -5,7 +5,7 @@
{% block title 'Remove activity'|trans %} {% block title 'Remove activity'|trans %}
{% block personcontent %} {% block content %}
{{ include('@ChillMain/Util/confirmation_template.html.twig', {{ include('@ChillMain/Util/confirmation_template.html.twig',
{ {
'title' : 'Remove activity'|trans, 'title' : 'Remove activity'|trans,

View File

@ -35,7 +35,7 @@
{{ form_row(edit_form.socialActions) }} {{ form_row(edit_form.socialActions) }}
{% endif %} {% endif %}
{%- if edit_form.socialIssues is defined or edit_form.socialIssues is defined -%} {%- if edit_form.socialIssues is defined or edit_form.socialActions is defined -%}
<div id="social-issues-acc"></div> <div id="social-issues-acc"></div>
{% endif %} {% endif %}

View File

@ -30,4 +30,5 @@
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }} {{ encore_entry_link_tags('vue_activity') }}
{{ encore_entry_link_tags('page_edit_activity') }}
{% endblock %} {% endblock %}

View File

@ -20,7 +20,7 @@
{% block title 'Update activity'|trans %} {% block title 'Update activity'|trans %}
{% block personcontent %} {% block content %}
<div class="activity-edit"> <div class="activity-edit">
<div id="activity"></div> {# <=== vue component #} <div id="activity"></div> {# <=== vue component #}

View File

@ -30,7 +30,7 @@
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
{% endblock %} {% endblock %}
{% block personcontent %} {% block content %}
{% set person_id = null %} {% set person_id = null %}
{% if person %} {% if person %}
@ -46,7 +46,7 @@
{% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'person'} %} {% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'person'} %}
{% if is_granted('CHILL_ACTIVITY_CREATE', person) %} {% if is_granted('CHILL_ACTIVITY_CREATE_PERSON', person) %}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li> <li>
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" <a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}"

View File

@ -35,7 +35,7 @@
{{ form_row(form.socialActions) }} {{ form_row(form.socialActions) }}
{% endif %} {% endif %}
{%- if form.socialIssues is defined or form.socialIssues is defined -%} {%- if form.socialIssues is defined or form.socialActions is defined -%}
<div id="social-issues-acc"></div> <div id="social-issues-acc"></div>
{% endif %} {% endif %}
@ -85,15 +85,15 @@
{{ form_row(form.comment) }} {{ form_row(form.comment) }}
{% endif %} {% endif %}
{%- if form.attendee is defined -%}
{{ form_row(form.attendee) }}
{% endif %}
{%- if form.documents is defined -%} {%- if form.documents is defined -%}
{{ form_row(form.documents) }} {{ form_row(form.documents) }}
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\ActivityBundle\Entity\Activity" data-entity-id="{{ entity.id }}"></div> <div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\ActivityBundle\Entity\Activity" data-entity-id="{{ entity.id }}"></div>
{% endif %} {% endif %}
{%- if form.attendee is defined -%}
{{ form_row(form.attendee) }}
{% endif %}
{# TODO .. status #} {# TODO .. status #}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">

View File

@ -4,7 +4,7 @@
{% block title 'Activity creation' |trans %} {% block title 'Activity creation' |trans %}
{% block personcontent %} {% block content %}
<div class="activity-new"> <div class="activity-new">
<div id="activity"></div> {# <=== vue component #} <div id="activity"></div> {# <=== vue component #}

View File

@ -4,6 +4,6 @@
{% block title 'Activity creation'|trans %} {% block title 'Activity creation'|trans %}
{% block personcontent %} {% block content %}
{% include 'ChillActivityBundle:Activity:selectType.html.twig' %} {% include 'ChillActivityBundle:Activity:selectType.html.twig' %}
{% endblock %} {% endblock %}

View File

@ -18,11 +18,11 @@
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
{% block personcontent -%} {% block content -%}
<div class="activity-show"> <div class="activity-show">
{% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'person'} %} {% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'person'} %}
</div> </div>
{% endblock personcontent %} {% endblock %}
{% block block_post_menu %} {% block block_post_menu %}
<div class="post-menu pt-4"> <div class="post-menu pt-4">

View File

@ -25,7 +25,7 @@
{% endblock %} {% endblock %}
{% block layout_wvm_content %} {% block layout_wvm_content %}
{% block admin_content %}<!-- block personcontent empty --> {% block admin_content %}<!-- block content empty -->
<h1>{{ 'Activity configuration' |trans }}</h1> <h1>{{ 'Activity configuration' |trans }}</h1>
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,286 @@
<?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\Service\DocGenerator;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Service\DocGenerator\AccompanyingPeriodContext;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use DateTime;
use libphonenumber\PhoneNumber;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class ListActivitiesByAccompanyingPeriodContext implements
DocGeneratorContextWithAdminFormInterface,
DocGeneratorContextWithPublicFormInterface
{
private AccompanyingPeriodContext $accompanyingPeriodContext;
private ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
private NormalizerInterface $normalizer;
private PersonRepository $personRepository;
private SocialActionRepository $socialActionRepository;
private SocialIssueRepository $socialIssueRepository;
private ThirdPartyRepository $thirdPartyRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
private UserRepository $userRepository;
public function __construct(
AccompanyingPeriodContext $accompanyingPeriodContext,
ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
NormalizerInterface $normalizer,
PersonRepository $personRepository,
SocialActionRepository $socialActionRepository,
SocialIssueRepository $socialIssueRepository,
ThirdPartyRepository $thirdPartyRepository,
TranslatableStringHelperInterface $translatableStringHelper,
UserRepository $userRepository
) {
$this->accompanyingPeriodContext = $accompanyingPeriodContext;
$this->activityACLAwareRepository = $activityACLAwareRepository;
$this->normalizer = $normalizer;
$this->personRepository = $personRepository;
$this->socialActionRepository = $socialActionRepository;
$this->socialIssueRepository = $socialIssueRepository;
$this->thirdPartyRepository = $thirdPartyRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->userRepository = $userRepository;
}
public function adminFormReverseTransform(array $data): array
{
return $this->accompanyingPeriodContext->adminFormReverseTransform($data);
}
public function adminFormTransform(array $data): array
{
return $this->accompanyingPeriodContext->adminFormTransform($data);
}
public function buildAdminForm(FormBuilderInterface $builder): void
{
$this->accompanyingPeriodContext->buildAdminForm($builder);
}
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{
$this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity);
}
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
{
$data = $this->accompanyingPeriodContext->getData($template, $entity, $contextGenerationData);
$data['activities'] = $this->getActivitiesSimplified($entity);
return $data;
}
public function getDescription(): string
{
return 'docgen.Accompanying period with a list of activities description';
}
public function getEntityClass(): string
{
return AccompanyingPeriod::class;
}
public function getFormData(DocGeneratorTemplate $template, $entity): array
{
return $this->accompanyingPeriodContext->getFormData($template, $entity);
}
public static function getKey(): string
{
return self::class;
}
public function getName(): string
{
return 'docgen.Accompanying period with a list of activities';
}
public function hasAdminForm(): bool
{
return $this->accompanyingPeriodContext->hasAdminForm();
}
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
{
return $this->accompanyingPeriodContext->hasPublicForm($template, $entity);
}
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData);
}
private function getActivitiesSimplified(AccompanyingPeriod $period)
{
$activities =
$this->activityACLAwareRepository->findByAccompanyingPeriodSimplified($period);
$results = [];
foreach ($activities as $row) {
$activity = $row[0];
$activity['date'] = $this->normalizer->normalize($activity['date'], 'docgen', [
AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => DateTime::class,
]);
if (null === $activity['location']) {
$activity['location'] = $this->normalizer->normalize(null, 'docgen', [
AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class,
]);
$activity['location']['type'] = 'location';
} else {
$activity['location']['isNull'] = false;
$activity['location']['type'] = 'location';
foreach (['1', '2'] as $key) {
$activity['location']['phonenumber' . $key] = $this->normalizer->normalize(
$activity['location']['phonenumber' . $key],
'docgen',
[AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => PhoneNumber::class]
);
}
}
if (is_numeric($activity['location']['locationType']['id'])) {
$activity['location']['locationType']['title'] = $this->translatableStringHelper->localize(
$activity['location']['locationType']['title']
);
$activity['location']['locationType']['isNull'] = false;
$activity['location']['locationType']['type'] = 'locationType';
}
if (null !== $activity['activityType']) {
$activity['activityType']['name'] = $this->translatableStringHelper->localize(
$activity['activityType']['name']
);
$activity['activityType']['isNull'] = false;
$activity['activityType']['type'] = 'activityType';
} else {
$activity['activityType'] = $this->normalizer->normalize(null, 'docgen', [
AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class,
]);
}
if (null !== $activity['attendee']) {
$activity['attendee']['name'] = $this->translatableStringHelper->localize(
$activity['attendee']['name']
);
$activity['attendee']['isNull'] = false;
$activity['attendee']['type'] = 'activityPresence';
} else {
$activity['attendee'] = $this->normalizer->normalize(null, 'docgen', [
AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class,
]);
}
$activity['comment'] = (string) $row['comment'];
$activity['travelTimeMinute'] = $row['travelTimeMinute'];
$activity['durationTimeMinute'] = $row['durationTimeMinute'];
if (null !== $row['userIds']) {
foreach ($row['userIds'] as $id) {
$activity['users'][] = $this->normalizer->normalize(
$this->userRepository->find($id),
'docgen',
[AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class]
);
}
} else {
$activity['users'] = [];
}
if (null !== $row['personIds']) {
foreach ($row['personIds'] as $id) {
$activity['persons'][] = $this->normalizer->normalize(
$this->personRepository->find($id),
'docgen',
[AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Person::class]
);
}
} else {
$activity['persons'] = [];
}
if (null !== $row['thirdPartyIds']) {
foreach ($row['thirdPartyIds'] as $id) {
$activity['thirdParties'][] = $this->normalizer->normalize(
$this->thirdPartyRepository->find($id),
'docgen',
[AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ThirdParty::class]
);
}
} else {
$activity['thirdParties'] = [];
}
if (null !== $row['socialActionIds']) {
foreach ($row['socialActionIds'] as $id) {
$activity['socialActions'][] = $this->normalizer->normalize(
$this->socialActionRepository->find($id),
'docgen',
[AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialAction::class]
);
}
} else {
$activity['socialActions'] = [];
}
if (null !== $row['socialIssueIds']) {
foreach ($row['socialIssueIds'] as $id) {
$activity['socialIssues'][] = $this->normalizer->normalize(
$this->socialIssueRepository->find($id),
'docgen',
[AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialIssue::class]
);
}
} else {
$activity['socialIssues'] = [];
}
$results[] = $activity;
}
return $results;
}
}

View File

@ -7,5 +7,7 @@ module.exports = function(encore, entries)
ChillActivityAssets: __dirname + '/Resources/public' ChillActivityAssets: __dirname + '/Resources/public'
}); });
encore.addEntry('page_edit_activity', __dirname + '/Resources/public/page/edit_activity/index.scss');
encore.addEntry('vue_activity', __dirname + '/Resources/public/vuejs/Activity/index.js'); encore.addEntry('vue_activity', __dirname + '/Resources/public/vuejs/Activity/index.js');
}; };

View File

@ -66,6 +66,14 @@ Choose a type: Choisir un type
1 hour 30: 1 heure 30 1 hour 30: 1 heure 30
1 hour 45: 1 heure 45 1 hour 45: 1 heure 45
2 hours: 2 heures 2 hours: 2 heures
2 hours 15: 2 heures 15
2 hours 30: 2 heures 30
2 hours 45: 2 heures 45
3 hours: 3 heures
3 hours 30: 3 heures 30
4 hours: 4 heures
4 hours 30: 4 heures 30
5 hours: 5 heures
Concerned groups: Parties concernées Concerned groups: Parties concernées
Persons in accompanying course: Usagers du parcours Persons in accompanying course: Usagers du parcours
Third persons: Tiers non-pro. Third persons: Tiers non-pro.
@ -232,3 +240,5 @@ This is the minimal activity data: Activité n°
docgen: docgen:
Activity basic: Echange Activity basic: Echange
A basic context for activity: Contexte pour les échanges A basic context for activity: Contexte pour les échanges
Accompanying period with a list of activities: Parcours d'accompagnement avec liste des échanges
Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les échanges pour un parcours. Les échanges ne sont pas filtrés.

View File

@ -31,6 +31,10 @@ class Configuration implements ConfigurationInterface
->isRequired() ->isRequired()
->defaultValue( ->defaultValue(
[ [
['label' => '1 minutes', 'seconds' => 60],
['label' => '2 minutes', 'seconds' => 120],
['label' => '3 minutes', 'seconds' => 180],
['label' => '4 minutes', 'seconds' => 240],
['label' => '5 minutes', 'seconds' => 300], ['label' => '5 minutes', 'seconds' => 300],
['label' => '10 minutes', 'seconds' => 600], ['label' => '10 minutes', 'seconds' => 600],
['label' => '15 minutes', 'seconds' => 900], ['label' => '15 minutes', 'seconds' => 900],

View File

@ -6,7 +6,7 @@
{% block layout_wvm_content %} {% block layout_wvm_content %}
{% block admin_content %} {% block admin_content %}
<!-- block personcontent empty --> <!-- block content empty -->
<h1>{{ 'Aside activity configuration'|trans }}</h1> <h1>{{ 'Aside activity configuration'|trans }}</h1>
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}

View File

@ -40,7 +40,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="item-col"> <div class="item-col" style="justify-content: flex-end;">
<div class="box"> <div class="box">
<ul class="list-content fa-ul"> <ul class="list-content fa-ul">
<li> <li>
@ -79,7 +79,7 @@
{{ chill_pagination(paginator) }} {{ chill_pagination(paginator) }}
<ul class="record_actions"> <ul class="record_actions sticky-form-buttons">
<li> <li>
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_new') }}" class="btn btn-create"> <a href="{{ chill_path_add_return_path('chill_crud_aside_activity_new') }}" class="btn btn-create">
{{ 'Create'|trans }} {{ 'Create'|trans }}

View File

@ -9,9 +9,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Calculator; namespace Chill\BudgetBundle\Calculator;
use Chill\AMLI\BudgetBundle\Entity\AbstractElement; use Chill\BudgetBundle\Entity\AbstractElement;
interface CalculatorInterface interface CalculatorInterface
{ {

View File

@ -9,9 +9,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Calculator; namespace Chill\BudgetBundle\Calculator;
use Chill\AMLI\BudgetBundle\Entity\AbstractElement; use Chill\BudgetBundle\Entity\AbstractElement;
use OutOfBoundsException; use OutOfBoundsException;
use function array_key_exists; use function array_key_exists;

View File

@ -9,7 +9,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Calculator; namespace Chill\BudgetBundle\Calculator;
class CalculatorResult class CalculatorResult
{ {

View File

@ -9,12 +9,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle; namespace Chill\BudgetBundle;
use Chill\AMLI\BudgetBundle\DependencyInjection\Compiler\CalculatorCompilerPass; use Chill\BudgetBundle\DependencyInjection\Compiler\CalculatorCompilerPass;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillAMLIBudgetBundle extends Bundle class ChillBudgetBundle extends Bundle
{ {
public function build(\Symfony\Component\DependencyInjection\ContainerBuilder $container) public function build(\Symfony\Component\DependencyInjection\ContainerBuilder $container)
{ {

View File

@ -9,7 +9,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Config; namespace Chill\BudgetBundle\Config;
class ConfigRepository class ConfigRepository
{ {
@ -29,6 +29,11 @@ class ConfigRepository
$this->charges = $charges; $this->charges = $charges;
} }
public function getChargesKeys(): array
{
return array_map(static function ($element) { return $element['key']; }, $this->charges);
}
/** /**
* @return array where keys are the resource'key and label the ressource label * @return array where keys are the resource'key and label the ressource label
*/ */
@ -43,6 +48,11 @@ class ConfigRepository
return $charges; return $charges;
} }
public function getResourcesKeys(): array
{
return array_map(static function ($element) { return $element['key']; }, $this->resources);
}
/** /**
* @return array where keys are the resource'key and label the ressource label * @return array where keys are the resource'key and label the ressource label
*/ */

View File

@ -9,37 +9,29 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Controller; namespace Chill\BudgetBundle\Controller;
use Chill\AMLI\BudgetBundle\Entity\AbstractElement; use Chill\BudgetBundle\Entity\AbstractElement;
use Chill\AMLI\BudgetBundle\Security\Authorization\BudgetElementVoter; use Chill\BudgetBundle\Security\Authorization\BudgetElementVoter;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use function get_class; use function get_class;
abstract class AbstractElementController extends Controller abstract class AbstractElementController extends AbstractController
{ {
/** protected LoggerInterface $chillMainLogger;
* @var LoggerInterface
*/
protected $chillMainLogger;
/** protected EntityManagerInterface $em;
* @var EntityManagerInterface
*/
protected $em;
/** protected TranslatorInterface $translator;
* @var TranslatorInterface
*/
protected $translator;
public function __construct( public function __construct(
EntityManagerInterface $em, EntityManagerInterface $em,
@ -59,16 +51,22 @@ abstract class AbstractElementController extends Controller
* *
* @param mixed $template * @param mixed $template
* @param mixed $flashMessage * @param mixed $flashMessage
*
* @return \Symfony\Component\BrowserKit\Response
*/ */
protected function _delete(AbstractElement $element, Request $request, $template, $flashMessage) protected function _delete(AbstractElement $element, Request $request, $template, $flashMessage): Response
{ {
$this->denyAccessUnlessGranted(BudgetElementVoter::DELETE, $element, 'You are not ' $this->denyAccessUnlessGranted(BudgetElementVoter::DELETE, $element, 'You are not '
. 'allowed to delete this family membership'); . 'allowed to delete this item');
$form = $this->createDeleteForm(); $form = $this->createDeleteForm();
if (null !== $element->getPerson()) {
$entity = $element->getPerson();
$indexPage = 'chill_budget_elements_index';
} else {
$entity = $element->getHousehold();
$indexPage = 'chill_budget_elements_household_index';
}
if ($request->getMethod() === Request::METHOD_DELETE) { if ($request->getMethod() === Request::METHOD_DELETE) {
$form->handleRequest($request); $form->handleRequest($request);
@ -88,8 +86,8 @@ abstract class AbstractElementController extends Controller
$this->addFlash('success', $this->translator $this->addFlash('success', $this->translator
->trans($flashMessage)); ->trans($flashMessage));
return $this->redirectToRoute('chill_budget_elements_index', [ return $this->redirectToRoute($indexPage, [
'id' => $element->getPerson()->getId(), 'id' => $entity->getId(),
]); ]);
} }
} }
@ -103,13 +101,23 @@ abstract class AbstractElementController extends Controller
/** /**
* @param string $template * @param string $template
* @param string $flashOnSuccess * @param string $flashOnSuccess
*
* @return \Symfony\Component\HttpFoundation\Response
*/ */
protected function _edit(AbstractElement $element, Request $request, $template, $flashOnSuccess) protected function _edit(AbstractElement $element, Request $request, $template, $flashOnSuccess): Response
{ {
$this->denyAccessUnlessGranted(BudgetElementVoter::UPDATE, $element); $this->denyAccessUnlessGranted(BudgetElementVoter::UPDATE, $element);
if (null !== $element->getPerson()) {
$entity = $element->getPerson();
$entityStr = 'person';
$indexPage = 'chill_budget_elements_index';
} else {
$entity = $element->getHousehold();
$entityStr = 'household';
$indexPage = 'chill_budget_elements_household_index';
}
$entity = null !== $element->getPerson() ? $element->getPerson() : $element->getHousehold();
$form = $this->createForm($this->getType(), $element); $form = $this->createForm($this->getType(), $element);
$form->add('submit', SubmitType::class); $form->add('submit', SubmitType::class);
@ -121,29 +129,39 @@ abstract class AbstractElementController extends Controller
$this->addFlash('success', $this->translator->trans($flashOnSuccess)); $this->addFlash('success', $this->translator->trans($flashOnSuccess));
return $this->redirectToRoute('chill_budget_elements_index', [ return $this->redirectToRoute($indexPage, [
'id' => $element->getPerson()->getId(), 'id' => $entity->getId(),
]); ]);
} }
return $this->render($template, [ return $this->render($template, [
'element' => $element, 'element' => $element,
'form' => $form->createView(), 'form' => $form->createView(),
'person' => $element->getPerson(), $entityStr => $entity,
]); ]);
} }
/** /**
* @param mixed $template * @param mixed $template
* @param mixed $flashMessageOnSuccess * @param mixed $flashMessageOnSuccess
* @param mixed $entity
*/ */
protected function _new(Person $person, Request $request, $template, $flashMessageOnSuccess) protected function _new($entity, Request $request, $template, $flashMessageOnSuccess)
{ {
/** @var \Chill\AMLI\BudgetBundle\Entity\AbstractElement $element */ /** @var AbstractElement $element */
$element = $this->createNewElement() $element = $this->createNewElement();
->setPerson($person);
$this->denyAccessUnlessGranted(BudgetElementVoter::CREATE, $element); if ($entity instanceof Person) {
$element->setPerson($entity);
$entityStr = 'person';
$indexPage = 'chill_budget_elements_index';
} else {
$element->setHousehold($entity);
$entityStr = 'household';
$indexPage = 'chill_budget_elements_household_index';
}
// $this->denyAccessUnlessGranted(BudgetElementVoter::CREATE, $entity);
$form = $this->createForm($this->getType(), $element); $form = $this->createForm($this->getType(), $element);
$form->add('submit', SubmitType::class); $form->add('submit', SubmitType::class);
@ -157,8 +175,8 @@ abstract class AbstractElementController extends Controller
$this->addFlash('success', $this->translator->trans($flashMessageOnSuccess)); $this->addFlash('success', $this->translator->trans($flashMessageOnSuccess));
return $this->redirectToRoute('chill_budget_elements_index', [ return $this->redirectToRoute($indexPage, [
'id' => $person->getId(), 'id' => $entity->getId(),
]); ]);
} }
@ -168,7 +186,7 @@ abstract class AbstractElementController extends Controller
return $this->render($template, [ return $this->render($template, [
'form' => $form->createView(), 'form' => $form->createView(),
'person' => $person, $entityStr => $entity,
'element' => $element, 'element' => $element,
]); ]);
} }
@ -183,7 +201,7 @@ abstract class AbstractElementController extends Controller
*/ */
protected function _view(AbstractElement $element, $template) protected function _view(AbstractElement $element, $template)
{ {
$this->denyAccessUnlessGranted(BudgetElementVoter::SHOW, $element); $this->denyAccessUnlessGranted(BudgetElementVoter::SEE, $element);
return $this->render($template, [ return $this->render($template, [
'element' => $element, 'element' => $element,
@ -199,10 +217,8 @@ abstract class AbstractElementController extends Controller
/** /**
* Creates a form to delete a help request entity by id. * Creates a form to delete a help request entity by id.
*
* @return \Symfony\Component\Form\Form The form
*/ */
private function createDeleteForm() private function createDeleteForm(): Form
{ {
return $this->createFormBuilder() return $this->createFormBuilder()
->setMethod(Request::METHOD_DELETE) ->setMethod(Request::METHOD_DELETE)

View File

@ -9,10 +9,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Controller; namespace Chill\BudgetBundle\Controller;
use Chill\AMLI\BudgetBundle\Entity\Charge; use Chill\BudgetBundle\Entity\Charge;
use Chill\AMLI\BudgetBundle\Form\ChargeType; use Chill\BudgetBundle\Form\ChargeType;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -32,7 +33,7 @@ class ChargeController extends AbstractElementController
return $this->_delete( return $this->_delete(
$charge, $charge,
$request, $request,
'@ChillAMLIBudget/Charge/confirm_delete.html.twig', '@ChillBudget/Charge/confirm_delete.html.twig',
'Charge deleted' 'Charge deleted'
); );
} }
@ -50,7 +51,7 @@ class ChargeController extends AbstractElementController
return $this->_edit( return $this->_edit(
$charge, $charge,
$request, $request,
'@ChillAMLIBudget/Charge/edit.html.twig', '@ChillBudget/Charge/edit.html.twig',
'Charge updated' 'Charge updated'
); );
} }
@ -68,7 +69,25 @@ class ChargeController extends AbstractElementController
return $this->_new( return $this->_new(
$person, $person,
$request, $request,
'@ChillAMLIBudget/Charge/new.html.twig', '@ChillBudget/Charge/new.html.twig',
'Charge created'
);
}
/**
* @Route(
* "{_locale}/budget/charge/by-household/{id}/new",
* name="chill_budget_charge_household_new"
* )
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function newHouseholdAction(Request $request, Household $household)
{
return $this->_new(
$household,
$request,
'@ChillBudget/Charge/new.html.twig',
'Charge created' 'Charge created'
); );
} }
@ -83,7 +102,7 @@ class ChargeController extends AbstractElementController
*/ */
public function viewAction(Charge $charge) public function viewAction(Charge $charge)
{ {
return $this->_view($charge, '@ChillAMLIBudget/Charge/view.html.twig'); return $this->_view($charge, '@ChillBudget/Charge/view.html.twig');
} }
protected function createNewElement() protected function createNewElement()

View File

@ -9,44 +9,33 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Controller; namespace Chill\BudgetBundle\Controller;
use Chill\AMLI\BudgetBundle\Calculator\CalculatorManager; use Chill\BudgetBundle\Calculator\CalculatorManager;
use Chill\AMLI\BudgetBundle\Entity\Charge; use Chill\BudgetBundle\Entity\Charge;
use Chill\AMLI\BudgetBundle\Entity\Resource; use Chill\BudgetBundle\Entity\Resource;
use Chill\AMLI\BudgetBundle\Security\Authorization\BudgetElementVoter; use Chill\BudgetBundle\Security\Authorization\BudgetElementVoter;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTime; use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use function array_merge; use function array_merge;
use function count; use function count;
class ElementController extends Controller class ElementController extends AbstractController
{ {
/** protected CalculatorManager $calculator;
* @var CalculatorManager
*/
protected $calculator;
/** protected LoggerInterface $chillMainLogger;
* @var LoggerInterface
*/
protected $chillMainLogger;
/** protected EntityManagerInterface $em;
* @var EntityManagerInterface
*/
protected $em;
/** protected TranslatorInterface $translator;
* @var TranslatorInterface
*/
protected $translator;
public function __construct( public function __construct(
EntityManagerInterface $em, EntityManagerInterface $em,
@ -68,11 +57,12 @@ class ElementController extends Controller
*/ */
public function indexAction(Person $person) public function indexAction(Person $person)
{ {
$this->denyAccessUnlessGranted(BudgetElementVoter::SHOW, $person); $this->denyAccessUnlessGranted(BudgetElementVoter::SEE, $person);
$charges = $this->em $charges = $this->em
->getRepository(Charge::class) ->getRepository(Charge::class)
->findByPerson($person); ->findByPerson($person);
$ressources = $this->em $ressources = $this->em
->getRepository(Resource::class) ->getRepository(Resource::class)
->findByPerson($person); ->findByPerson($person);
@ -81,10 +71,10 @@ class ElementController extends Controller
$actualCharges = $this->em $actualCharges = $this->em
->getRepository(Charge::class) ->getRepository(Charge::class)
->findByPersonAndDate($person, $now); ->findByEntityAndDate($person, $now);
$actualResources = $this->em $actualResources = $this->em
->getRepository(Resource::class) ->getRepository(Resource::class)
->findByPersonAndDate($person, $now); ->findByEntityAndDate($person, $now);
$elements = array_merge($actualCharges, $actualResources); $elements = array_merge($actualCharges, $actualResources);
@ -92,11 +82,79 @@ class ElementController extends Controller
$results = $this->calculator->calculateDefault($elements); $results = $this->calculator->calculateDefault($elements);
} }
return $this->render('ChillAMLIBudgetBundle:Element:index.html.twig', [ return $this->render('ChillBudgetBundle:Person:index.html.twig', [
'person' => $person, 'person' => $person,
'charges' => $charges, 'charges' => $charges,
'resources' => $ressources, 'resources' => $ressources,
'results' => $results ?? [], 'results' => $results ?? [],
]); ]);
} }
/**
* @Route(
* "{_locale}/budget/elements/by-household/{id}",
* name="chill_budget_elements_household_index"
* )
*/
public function indexHouseholdAction(Household $household)
{
$this->denyAccessUnlessGranted(BudgetElementVoter::SEE, $household);
$charges = $this->em
->getRepository(Charge::class)
->findByHousehold($household);
$ressources = $this->em
->getRepository(Resource::class)
->findByHousehold($household);
$now = new DateTime('now');
$actualCharges = $this->em
->getRepository(Charge::class)
->findByEntityAndDate($household, $now);
$actualResources = $this->em
->getRepository(Resource::class)
->findByEntityAndDate($household, $now);
$elements = array_merge($actualCharges, $actualResources);
if (count($elements) > 0) {
$results = $this->calculator->calculateDefault($elements);
}
// quick solution to calculate the sum, difference and amount from
// controller. This should be done from the calculators
// TODO replace this by calculators
$wholeCharges = $actualCharges;
$wholeResources = $actualResources;
foreach ($household->getCurrentPersons() as $person) {
$wholeCharges = array_merge(
$wholeCharges,
$this->em
->getRepository(Charge::class)
->findByEntityAndDate($person, $now)
);
$wholeResources = array_merge(
$wholeResources,
$this->em
->getRepository(Resource::class)
->findByEntityAndDate($person, $now)
);
}
return $this->render('ChillBudgetBundle:Household:index.html.twig', [
'household' => $household,
'charges' => $charges,
'resources' => $ressources,
'wholeResources' => array_filter($wholeResources, static function (Resource $r) use ($now) {
return $r->getStartDate() <= $now && ($r->getEndDate() === null || $r->getEndDate() >= $now);
}),
'wholeCharges' => array_filter($wholeCharges, static function (Charge $c) use ($now) {
return $c->getStartDate() <= $now && ($c->getEndDate() === null || $c->getEndDate() >= $now);
}),
'results' => $results ?? [],
]);
}
} }

View File

@ -9,13 +9,15 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Controller; namespace Chill\BudgetBundle\Controller;
use Chill\AMLI\BudgetBundle\Entity\Resource; use Chill\BudgetBundle\Entity\Resource;
use Chill\AMLI\BudgetBundle\Form\ResourceType; use Chill\BudgetBundle\Form\ResourceType;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ResourceController extends AbstractElementController class ResourceController extends AbstractElementController
{ {
@ -24,15 +26,13 @@ class ResourceController extends AbstractElementController
* "{_locale}/budget/resource/{id}/delete", * "{_locale}/budget/resource/{id}/delete",
* name="chill_budget_resource_delete" * name="chill_budget_resource_delete"
* ) * )
*
* @return \Symfony\Component\HttpFoundation\Response
*/ */
public function deleteAction(Request $request, Resource $resource) public function deleteAction(Request $request, Resource $resource)
{ {
return $this->_delete( return $this->_delete(
$resource, $resource,
$request, $request,
'@ChillAMLIBudget/Resource/confirm_delete.html.twig', '@ChillBudget/Resource/confirm_delete.html.twig',
'Resource deleted' 'Resource deleted'
); );
} }
@ -42,33 +42,49 @@ class ResourceController extends AbstractElementController
* "{_locale}/budget/resource/{id}/edit", * "{_locale}/budget/resource/{id}/edit",
* name="chill_budget_resource_edit" * name="chill_budget_resource_edit"
* ) * )
*
* @return \Symfony\Component\HttpFoundation\Response
*/ */
public function editAction(Request $request, Resource $resource) public function editAction(Request $request, Resource $resource): Response
{ {
return $this->_edit( return $this->_edit(
$resource, $resource,
$request, $request,
'@ChillAMLIBudget/Resource/edit.html.twig', '@ChillBudget/Resource/edit.html.twig',
'Resource updated' 'Resource updated'
); );
} }
/** /**
* Create a new budget element for a person.
*
* @Route( * @Route(
* "{_locale}/budget/resource/by-person/{id}/new", * "{_locale}/budget/resource/by-person/{id}/new",
* name="chill_budget_resource_new" * name="chill_budget_resource_new"
* ) * )
*
* @return \Symfony\Component\HttpFoundation\Response
*/ */
public function newAction(Request $request, Person $person) public function newAction(Request $request, Person $person): Response
{ {
return $this->_new( return $this->_new(
$person, $person,
$request, $request,
'@ChillAMLIBudget/Resource/new.html.twig', '@ChillBudget/Resource/new.html.twig',
'Resource created'
);
}
/**
* Create new budget element for a household.
*
* @Route(
* "{_locale}/budget/resource/by-household/{id}/new",
* name="chill_budget_resource_household_new"
* )
*/
public function newHouseholdAction(Request $request, Household $household): Response
{
return $this->_new(
$household,
$request,
'@ChillBudget/Resource/new.html.twig',
'Resource created' 'Resource created'
); );
} }
@ -78,12 +94,10 @@ class ResourceController extends AbstractElementController
* "{_locale}/budget/resource/{id}/view", * "{_locale}/budget/resource/{id}/view",
* name="chill_budget_resource_view" * name="chill_budget_resource_view"
* ) * )
*
* @return \Symfony\Component\HttpFoundation\Response
*/ */
public function viewAction(Resource $resource) public function viewAction(Resource $resource): Response
{ {
return $this->_view($resource, '@ChillAMLIBudget/Resource/view.html.twig'); return $this->_view($resource, '@ChillBudget/Resource/view.html.twig');
} }
protected function createNewElement() protected function createNewElement()

View File

@ -9,9 +9,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\DependencyInjection; namespace Chill\BudgetBundle\DependencyInjection;
use Chill\AMLI\BudgetBundle\Security\Authorization\BudgetElementVoter; use Chill\BudgetBundle\Security\Authorization\BudgetElementVoter;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
@ -23,11 +23,11 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
* *
* @see http://symfony.com/doc/current/cookbook/bundles/extension.html * @see http://symfony.com/doc/current/cookbook/bundles/extension.html
*/ */
class ChillAMLIBudgetExtension extends Extension implements PrependExtensionInterface class ChillBudgetExtension extends Extension implements PrependExtensionInterface
{ {
public function load(array $configs, ContainerBuilder $container) public function load(array $configs, ContainerBuilder $container)
{ {
$configuration = new Configuration(); $configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs); $config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
@ -38,6 +38,7 @@ class ChillAMLIBudgetExtension extends Extension implements PrependExtensionInte
$loader->load('services/templating.yaml'); $loader->load('services/templating.yaml');
$loader->load('services/menu.yaml'); $loader->load('services/menu.yaml');
$loader->load('services/calculator.yaml'); $loader->load('services/calculator.yaml');
$loader->load('services/services.yaml');
$this->storeConfig('resources', $config, $container); $this->storeConfig('resources', $config, $container);
$this->storeConfig('charges', $config, $container); $this->storeConfig('charges', $config, $container);
@ -58,7 +59,7 @@ class ChillAMLIBudgetExtension extends Extension implements PrependExtensionInte
$container->prependExtensionConfig('chill_main', [ $container->prependExtensionConfig('chill_main', [
'routing' => [ 'routing' => [
'resources' => [ 'resources' => [
'@ChillAMLIBudgetBundle/config/routing.yaml', '@ChillBudgetBundle/config/routing.yaml',
], ],
], ],
]); ]);
@ -68,8 +69,8 @@ class ChillAMLIBudgetExtension extends Extension implements PrependExtensionInte
{ {
$container->prependExtensionConfig('security', [ $container->prependExtensionConfig('security', [
'role_hierarchy' => [ 'role_hierarchy' => [
BudgetElementVoter::UPDATE => [BudgetElementVoter::SHOW], BudgetElementVoter::UPDATE => [BudgetElementVoter::SEE],
BudgetElementVoter::CREATE => [BudgetElementVoter::SHOW], BudgetElementVoter::CREATE => [BudgetElementVoter::SEE],
], ],
]); ]);
} }

View File

@ -9,7 +9,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\DependencyInjection\Compiler; namespace Chill\BudgetBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -19,7 +19,7 @@ class CalculatorCompilerPass implements CompilerPassInterface
{ {
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
$manager = $container->getDefinition('Chill\AMLI\BudgetBundle\Calculator\CalculatorManager'); $manager = $container->getDefinition('Chill\BudgetBundle\Calculator\CalculatorManager');
foreach ($container->findTaggedServiceIds('chill_budget.calculator') as $id => $tags) { foreach ($container->findTaggedServiceIds('chill_budget.calculator') as $id => $tags) {
foreach ($tags as $tag) { foreach ($tags as $tag) {

View File

@ -9,7 +9,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\DependencyInjection; namespace Chill\BudgetBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\ConfigurationInterface;
@ -23,14 +23,14 @@ class Configuration implements ConfigurationInterface
{ {
public function getConfigTreeBuilder() public function getConfigTreeBuilder()
{ {
$treeBuilder = new TreeBuilder('chill_amli_budget'); $treeBuilder = new TreeBuilder('chill_budget');
$rootNode = $treeBuilder->getRootNode('chill_amli_budget'); $rootNode = $treeBuilder->getRootNode('chill_budget');
$rootNode $rootNode
->children() ->children()
// ressources // ressources
->arrayNode('resources')->isRequired()->requiresAtLeastOneElement() ->arrayNode('resources')->defaultValue([])
->arrayPrototype() ->arrayPrototype()
->children() ->children()
->scalarNode('key')->isRequired()->cannotBeEmpty() ->scalarNode('key')->isRequired()->cannotBeEmpty()
@ -52,7 +52,7 @@ class Configuration implements ConfigurationInterface
->end() ->end()
->end() ->end()
->end() ->end()
->arrayNode('charges')->isRequired()->requiresAtLeastOneElement() ->arrayNode('charges')->defaultValue([])
->arrayPrototype() ->arrayPrototype()
->children() ->children()
->scalarNode('key')->isRequired()->cannotBeEmpty() ->scalarNode('key')->isRequired()->cannotBeEmpty()

View File

@ -9,13 +9,15 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Entity; namespace Chill\BudgetBundle\Entity;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTime; use DateTime;
use DateTimeImmutable; use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Type\Decimal;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
/** /**
@ -26,8 +28,6 @@ use Symfony\Component\Validator\Constraints as Assert;
abstract class AbstractElement abstract class AbstractElement
{ {
/** /**
* @var decimal
*
* @ORM\Column(name="amount", type="decimal", precision=10, scale=2) * @ORM\Column(name="amount", type="decimal", precision=10, scale=2)
* @Assert\GreaterThan( * @Assert\GreaterThan(
* value=0 * value=0
@ -36,100 +36,80 @@ abstract class AbstractElement
* message="The amount cannot be empty" * message="The amount cannot be empty"
* ) * )
*/ */
private $amount; private string $amount;
/** /**
* @var string|null
*
* @ORM\Column(name="comment", type="text", nullable=true) * @ORM\Column(name="comment", type="text", nullable=true)
*/ */
private $comment; private ?string $comment;
/** /**
* @var DateTimeImmutable|null
*
* @ORM\Column(name="endDate", type="datetime_immutable", nullable=true) * @ORM\Column(name="endDate", type="datetime_immutable", nullable=true)
* @Assert\GreaterThan( * @Assert\GreaterThan(
* propertyPath="startDate", * propertyPath="startDate",
* message="The budget element's end date must be after the start date" * message="The budget element's end date must be after the start date"
* ) * )
*/ */
private $endDate; private ?DateTimeImmutable $endDate;
/**
* @ORM\ManyToOne(
* targetEntity="\Chill\PersonBundle\Entity\Household\Household"
* )
*/
private ?Household $household = null;
/** /**
* @var Person
* @ORM\ManyToOne( * @ORM\ManyToOne(
* targetEntity="\Chill\PersonBundle\Entity\Person" * targetEntity="\Chill\PersonBundle\Entity\Person"
* ) * )
*/ */
private $person; private ?Person $person = null;
/** /**
* @var DateTimeImmutable
*
* @ORM\Column(name="startDate", type="datetime_immutable") * @ORM\Column(name="startDate", type="datetime_immutable")
* @Assert\Date * @Assert\Date
*/ */
private $startDate; private DateTimeImmutable $startDate;
/** /**
* @var string
*
* @ORM\Column(name="type", type="string", length=255) * @ORM\Column(name="type", type="string", length=255)
*/ */
private $type; private string $type;
/** /*Getters and Setters */
* Get amount.
* public function getAmount(): float
* @return float
*/
public function getAmount()
{ {
return (float) $this->amount; return (float) $this->amount;
} }
/** public function getComment(): ?string
* Get comment.
*
* @return string|null
*/
public function getComment()
{ {
return $this->comment; return $this->comment;
} }
/** public function getEndDate(): ?DateTimeImmutable
* Get endDate.
*
* @return DateTimeImmutable|null
*/
public function getEndDate()
{ {
return $this->endDate; return $this->endDate;
} }
public function getPerson(): Person public function getHousehold(): ?Household
{
return $this->household;
}
public function getPerson(): ?Person
{ {
return $this->person; return $this->person;
} }
/** public function getStartDate(): DateTimeImmutable
* Get startDate.
*
* @return DateTimeImmutable
*/
public function getStartDate()
{ {
return $this->startDate; return $this->startDate;
} }
/** public function getType(): string
* Get type.
*
* @return string
*/
public function getType()
{ {
return $this->type; return $this->type;
} }
@ -143,40 +123,21 @@ abstract class AbstractElement
abstract public function isResource(): bool; abstract public function isResource(): bool;
/** public function setAmount(string $amount): self
* Set amount.
*
* @param string $amount
*
* @return AbstractElement
*/
public function setAmount($amount)
{ {
$this->amount = $amount; $this->amount = $amount;
return $this; return $this;
} }
/** public function setComment(?string $comment = null): self
* Set comment.
*
* @param string|null $comment
*
* @return AbstractElement
*/
public function setComment($comment = null)
{ {
$this->comment = $comment; $this->comment = $comment;
return $this; return $this;
} }
/** public function setEndDate(?DateTimeInterface $endDate = null): self
* Set endDate.
*
* @return AbstractElement
*/
public function setEndDate(?DateTimeInterface $endDate = null)
{ {
if ($endDate instanceof DateTime) { if ($endDate instanceof DateTime) {
$this->endDate = DateTimeImmutable::createFromMutable($endDate); $this->endDate = DateTimeImmutable::createFromMutable($endDate);
@ -189,19 +150,21 @@ abstract class AbstractElement
return $this; return $this;
} }
public function setPerson(Person $person) public function setHousehold(Household $household): self
{
$this->household = $household;
return $this;
}
public function setPerson(Person $person): self
{ {
$this->person = $person; $this->person = $person;
return $this; return $this;
} }
/** public function setStartDate(DateTimeInterface $startDate): self
* Set startDate.
*
* @return AbstractElement
*/
public function setStartDate(DateTimeInterface $startDate)
{ {
if ($startDate instanceof DateTime) { if ($startDate instanceof DateTime) {
$this->startDate = DateTimeImmutable::createFromMutable($startDate); $this->startDate = DateTimeImmutable::createFromMutable($startDate);
@ -214,14 +177,7 @@ abstract class AbstractElement
return $this; return $this;
} }
/** public function setType(string $type): self
* Set type.
*
* @param string $type
*
* @return AbstractElement
*/
public function setType($type)
{ {
$this->type = $type; $this->type = $type;

View File

@ -9,8 +9,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Entity; namespace Chill\BudgetBundle\Entity;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@ -19,7 +20,7 @@ use Doctrine\ORM\Mapping as ORM;
* Charge. * Charge.
* *
* @ORM\Table(name="chill_budget.charge") * @ORM\Table(name="chill_budget.charge")
* @ORM\Entity(repositoryClass="Chill\AMLI\BudgetBundle\Repository\ChargeRepository") * @ORM\Entity(repositoryClass="Chill\BudgetBundle\Repository\ChargeRepository")
*/ */
class Charge extends AbstractElement implements HasCenterInterface class Charge extends AbstractElement implements HasCenterInterface
{ {
@ -58,7 +59,7 @@ class Charge extends AbstractElement implements HasCenterInterface
$this->setStartDate(new DateTimeImmutable('today')); $this->setStartDate(new DateTimeImmutable('today'));
} }
public function getCenter(): \Chill\MainBundle\Entity\Center public function getCenter(): ?Center
{ {
return $this->getPerson()->getCenter(); return $this->getPerson()->getCenter();
} }

View File

@ -9,8 +9,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Entity; namespace Chill\BudgetBundle\Entity;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@ -19,7 +20,7 @@ use Doctrine\ORM\Mapping as ORM;
* Resource. * Resource.
* *
* @ORM\Table(name="chill_budget.resource") * @ORM\Table(name="chill_budget.resource")
* @ORM\Entity(repositoryClass="Chill\AMLI\BudgetBundle\Repository\ResourceRepository") * @ORM\Entity(repositoryClass="Chill\BudgetBundle\Repository\ResourceRepository")
*/ */
class Resource extends AbstractElement implements HasCenterInterface class Resource extends AbstractElement implements HasCenterInterface
{ {
@ -37,7 +38,7 @@ class Resource extends AbstractElement implements HasCenterInterface
$this->setStartDate(new DateTimeImmutable('today')); $this->setStartDate(new DateTimeImmutable('today'));
} }
public function getCenter(): \Chill\MainBundle\Entity\Center public function getCenter(): ?Center
{ {
return $this->getPerson()->getCenter(); return $this->getPerson()->getCenter();
} }

View File

@ -9,10 +9,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Form; namespace Chill\BudgetBundle\Form;
use Chill\AMLI\BudgetBundle\Config\ConfigRepository; use Chill\BudgetBundle\Config\ConfigRepository;
use Chill\AMLI\BudgetBundle\Entity\Charge; use Chill\BudgetBundle\Entity\Charge;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
@ -45,6 +45,7 @@ class ChargeType extends AbstractType
->add('type', ChoiceType::class, [ ->add('type', ChoiceType::class, [
'choices' => $this->getTypes(), 'choices' => $this->getTypes(),
'placeholder' => 'Choose a charge type', 'placeholder' => 'Choose a charge type',
'attr' => ['class' => ' select2 '],
]) ])
->add('amount', MoneyType::class) ->add('amount', MoneyType::class)
->add('comment', TextareaType::class, [ ->add('comment', TextareaType::class, [
@ -96,7 +97,7 @@ class ChargeType extends AbstractType
public function getBlockPrefix() public function getBlockPrefix()
{ {
return 'chill_amli_budgetbundle_charge'; return 'chill_budgetbundle_charge';
} }
private function getTypes() private function getTypes()

View File

@ -9,10 +9,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Form; namespace Chill\BudgetBundle\Form;
use Chill\AMLI\BudgetBundle\Config\ConfigRepository; use Chill\BudgetBundle\Config\ConfigRepository;
use Chill\AMLI\BudgetBundle\Entity\Resource; use Chill\BudgetBundle\Entity\Resource;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
@ -45,6 +45,7 @@ class ResourceType extends AbstractType
'choices' => $this->getTypes(), 'choices' => $this->getTypes(),
'placeholder' => 'Choose a resource type', 'placeholder' => 'Choose a resource type',
'label' => 'Resource element type', 'label' => 'Resource element type',
'attr' => ['class' => ' select2 '],
]) ])
->add('amount', MoneyType::class) ->add('amount', MoneyType::class)
->add('comment', TextareaType::class, [ ->add('comment', TextareaType::class, [
@ -80,7 +81,7 @@ class ResourceType extends AbstractType
public function getBlockPrefix() public function getBlockPrefix()
{ {
return 'chill_amli_budgetbundle_resource'; return 'chill_budgetbundle_resource';
} }
private function getTypes() private function getTypes()

View File

@ -0,0 +1,53 @@
<?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\BudgetBundle\Menu;
use Chill\BudgetBundle\Security\Authorization\BudgetElementVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class HouseholdMenuBuilder implements LocalMenuBuilderInterface
{
protected AuthorizationCheckerInterface $authorizationChecker;
protected TranslatorInterface $translator;
public function __construct(
AuthorizationCheckerInterface $authorizationChecker,
TranslatorInterface $translator
) {
$this->authorizationChecker = $authorizationChecker;
$this->translator = $translator;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
/** @var Household $household */
$household = $parameters['household'];
// if ($this->authorizationChecker->isGranted(BudgetElementVoter::SHOW, $household)) {
$menu->addChild($this->translator->trans('household.Budget'), [
'route' => 'chill_budget_elements_household_index',
'routeParameters' => [
'id' => $household->getId(),
], ])
->setExtras(['order' => 19]);
// }
}
public static function getMenuIds(): array
{
return ['household'];
}
}

View File

@ -9,25 +9,19 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Menu; namespace Chill\BudgetBundle\Menu;
use Chill\AMLI\BudgetBundle\Security\Authorization\BudgetElementVoter; use Chill\BudgetBundle\Security\Authorization\BudgetElementVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem; use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class UserMenuBuilder implements LocalMenuBuilderInterface class PersonMenuBuilder implements LocalMenuBuilderInterface
{ {
/** protected AuthorizationCheckerInterface $authorizationChecker;
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
/** protected TranslatorInterface $translator;
* @var TranslatorInterface
*/
protected $translator;
public function __construct( public function __construct(
AuthorizationCheckerInterface $authorizationChecker, AuthorizationCheckerInterface $authorizationChecker,
@ -39,10 +33,10 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)
{ {
/** @var \Chill\PersonBundle\Entity\Person $person */ /** @var Person $person */
$person = $parameters['person']; $person = $parameters['person'];
if ($this->authorizationChecker->isGranted(BudgetElementVoter::SHOW, $person)) { if ($this->authorizationChecker->isGranted(BudgetElementVoter::SEE, $person)) {
$menu->addChild( $menu->addChild(
$this->translator->trans('Budget'), $this->translator->trans('Budget'),
[ [
@ -50,7 +44,7 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
'routeParameters' => ['id' => $person->getId()], 'routeParameters' => ['id' => $person->getId()],
] ]
) )
->setExtra('order', 460); ->setExtra('order', 59);
} }
} }

View File

@ -9,10 +9,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Repository; namespace Chill\BudgetBundle\Repository;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTime; use DateTime;
use Doctrine\ORM\EntityRepository;
/** /**
* ChargeRepository. * ChargeRepository.
@ -20,13 +21,15 @@ use DateTime;
* This class was generated by the Doctrine ORM. Add your own custom * This class was generated by the Doctrine ORM. Add your own custom
* repository methods below. * repository methods below.
*/ */
class ChargeRepository extends \Doctrine\ORM\EntityRepository class ChargeRepository extends EntityRepository
{ {
public function findByPersonAndDate(Person $person, DateTime $date, $sort = null) public function findByEntityAndDate($entity, DateTime $date, $sort = null)
{ {
$qb = $this->createQueryBuilder('c'); $qb = $this->createQueryBuilder('c');
$qb->where('c.person = :person') $entityStr = $entity instanceof Person ? 'person' : 'household';
$qb->where("c.{$entityStr} = :{$entityStr}")
->andWhere('c.startDate < :date') ->andWhere('c.startDate < :date')
->andWhere('c.startDate < :date OR c.startDate IS NULL'); ->andWhere('c.startDate < :date OR c.startDate IS NULL');
@ -35,7 +38,7 @@ class ChargeRepository extends \Doctrine\ORM\EntityRepository
} }
$qb->setParameters([ $qb->setParameters([
'person' => $person, $entityStr => $entity,
'date' => $date, 'date' => $date,
]); ]);

View File

@ -9,10 +9,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Repository; namespace Chill\BudgetBundle\Repository;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTime; use DateTime;
use Doctrine\ORM\EntityRepository;
/** /**
* ResourceRepository. * ResourceRepository.
@ -20,23 +21,28 @@ use DateTime;
* This class was generated by the Doctrine ORM. Add your own custom * This class was generated by the Doctrine ORM. Add your own custom
* repository methods below. * repository methods below.
*/ */
class ResourceRepository extends \Doctrine\ORM\EntityRepository class ResourceRepository extends EntityRepository
{ {
public function findByPersonAndDate(Person $person, DateTime $date, $sort = null) public function findByEntityAndDate($entity, DateTime $date, $sort = null)
{ {
$qb = $this->createQueryBuilder('c'); $qb = $this->createQueryBuilder('c');
$qb->where('c.person = :person') $entityStr = $entity instanceof Person ? 'person' : 'household';
->andWhere('c.startDate < :date')
->andWhere('c.startDate < :date OR c.startDate IS NULL'); $qb->where("c.{$entityStr} = :{$entityStr}")
// TODO: in controller, the budget and charges asked are also for future and actual
//->andWhere('c.startDate < :date')
// TODO: there is a misconception here, the end date must be lower or null. startDate are never null
//->andWhere('c.startDate < :date OR c.startDate IS NULL');
;
if (null !== $sort) { if (null !== $sort) {
$qb->orderBy($sort); $qb->orderBy($sort);
} }
$qb->setParameters([ $qb->setParameters([
'person' => $person, $entityStr => $entity,
'date' => $date, //'date' => $date,
]); ]);
return $qb->getQuery()->getResult(); return $qb->getQuery()->getResult();

View File

@ -0,0 +1,61 @@
.subtitle {
margin-top: 1rem;
margin-bottom: 1rem;
padding: 1rem;
}
.family-title {
margin-bottom: 1rem !important;
}
.budget-table th {
th {
color: white;
}
}
.budget-table {
th.charge {
background-color: #e03851d7;
}
}
.budget-table {
th.resource {
background-color: #6d9e63d8;
}
}
.budget-table {
th, td {
padding: 10px;
text-align: right;
}
td.column-wide {
width: 20%;
}
td.column-small {
width: 15%;
&.right {
align-items: right;
}
}
}
.btn-budget {
margin-right: 1rem !important;
}
.el-type {
font-weight: 600;
text-align: left !important;
}
.total {
margin-top: 1rem;
border-top: 1px dashed black;
}
.accordion-item {
margin-bottom: 1rem;
}
button[aria-expanded="true"] > span.folded,
button[aria-expanded="false"] > span.unfolded { display: none; }
button[aria-expanded="false"] > span.folded,
button[aria-expanded="true"] > span.unfolded { display: inline; }

View File

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

View File

@ -0,0 +1,74 @@
{% set entity = person is defined ? person : household %}
{% set actualResources = [] %}
{% set futureResources = [] %}
{% set pastResources = [] %}
{% for r in resources %}
{% if r.startDate|date('U') <= 'now'|date('U') %}
{% if r.endDate is null or r.endDate|date('U') >= 'now'|date('U') %}
{% set actualResources = actualResources|merge([ r ]) %}
{% else %}
{% set pastResources = pastResources|merge([ r ]) %}
{% endif %}
{% else %}
{% set futureResources = futureResources|merge([ r ]) %}
{% endif %}
{% endfor %}
{% set actualCharges = [] %}
{% set futureCharges = [] %}
{% set pastCharges = [] %}
{% for c in charges %}
{% if c.startDate|date('U') <= 'now'|date('U') %}
{% if c.endDate is null or c.endDate|date('U') >= 'now'|date('U') %}
{% set actualCharges = actualCharges|merge([ c ]) %}
{% else %}
{% set pastCharges = pastCharges|merge([ c ]) %}
{% endif %}
{% else %}
{% set futureCharges = futureCharges|merge([ c ]) %}
{% endif %}
{% endfor %}
<h3 class="subtitle">{{ 'Actual budget'|trans }}</h3>
{% if actualCharges|length > 0 or actualResources|length > 0 %}
{% include 'ChillBudgetBundle:Budget:_current_budget.html.twig' with {
'actualResources': actualResources,
'actualCharges': actualCharges,
'results': results,
'entity': entity
} %}
{% else %}
<div class="flex-table">
<div class="item-bloc">
<p><span class="chill-no-data-statement">{{ "There isn't any element recorded"|trans }}</span></p>
</div>
</div>
{% endif %}
{% if pastCharges|length > 0 or pastResources|length > 0 %}
<h2 class="subtitle">{{ 'Past budget'|trans }}</h2>
{% include 'ChillBudgetBundle:Budget:_past_budget.html.twig' with {
'pastCharges': pastCharges,
'pastResources': pastResources,
'entity': entity
} %}
{% endif %}
{% if futureCharges|length > 0 or futureResources|length > 0 %}
<h2 class="subtitle">{{ 'Future budget'|trans }}</h2>
{% include 'ChillBudgetBundle:Budget:_future_budget.html.twig' with {
'futureResources': futureResources,
'futureCharges': futureCharges,
'entity': entity
} %}
{% endif %}

View File

@ -0,0 +1,30 @@
{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_elements, table_results %}
{# <h2 class="subtitle">{{ 'Actual budget'|trans }}</h2> #}
<div class="flex-table">
<h4 class="family-title">{{ 'Actual resources'|trans }}</h4>
{% if actualResources|length > 0 %}
<div class="item-bloc">
{{ table_elements(actualResources, 'resource') }}
</div>
{% else %}
<div class="item-bloc">
<span class="chill-no-data-statement">{{ 'No resources registered'|trans }}</span>
</div>
{% endif %}
</div>
<div class="flex-table">
<h4 class="family-title">{{ 'Actual charges'|trans }}</h4>
{% if actualCharges|length > 0 %}
<div class="item-bloc">
{{ table_elements(actualCharges, 'charge') }}
</div>
{% else %}
<div class="item-bloc">
<span class="chill-no-data-statement">{{ 'No charges registered'|trans }}</span>
</div>
{% endif %}
</div>

View File

@ -0,0 +1,51 @@
{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_elements, table_results %}
<div class="accordion" id="future_{{ entity.id }}">
<div class="accordion-item">
<h2 class="accordion-header" id="heading_future_{{ entity.id }}">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse_future_{{ entity.id }}"
aria-expanded="false"
aria-controls="collapse_future_{{ entity.id }}">
<span class="folded">{{ 'Show future budget'|trans }}</span>
<span class="unfolded text-secondary">{{ 'Hide budget'|trans }}</span>
</button>
</h2>
<div id="collapse_future_{{ entity.id }}"
class="accordion-collapse collapse"
aria-labelledby="heading_future_{{ entity.id }}"
data-bs-parent="#future_{{ entity.id }}">
<div class="flex-table">
<h3 class="family-title">{{ 'Future resources'|trans }}</h3>
{% if futureResources|length > 0 %}
<div class="item-bloc">
{{ table_elements(futureResources, 'resource') }}
</div>
{% else %}
<div class="item-bloc">
<span class="chill-no-data-statement">{{ 'No future resources registered'|trans }}</span>
</div>
{% endif %}
</div>
<div class="flex-table">
<h3 class="family-title">{{ 'Future charges'|trans }}</h3>
{% if futureCharges|length > 0 %}
<div class="item-bloc">
{{ table_elements(futureCharges, 'charge') }}
</div>
{% else %}
<div class="item-bloc">
<span class="chill-no-data-statement">{{ 'No future charges registered'|trans }}</span>
</div>
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,97 @@
{% macro table_elements(elements, family) %}
<table class="budget-table">
<thead>
<tr>
<th class="{{ family }} el-type">{{ 'Budget element type'|trans }}</th>
<th class="{{ family }}">{{ 'Amount'|trans }}</th>
<th class="{{ family }}">{{ 'Validity period'|trans }}</th>
<th class="{{ family }}">&nbsp;</th>
</tr>
</thead>
<tbody>
{% set total = 0 %}
{% for f in elements %}
{% set total = total + f.amount %}
<tr>
<td class="column-wide el-type">
<span class="badge-title">
<span class="title_label title_label_{{ family }}"></span>
<span class="title_action">{{ f.type|budget_element_type_display(family) }}<span>
</span>
</td>
<td class="column-small">{{ f.amount|format_currency('EUR') }}</td>
<td class="column-wide">
{% if f.endDate is not null %}
{{ f.startDate|format_date('short') ~ ' - ' ~ f.endDate|format_date('short') }}
{% else %}
{{ f.startDate|format_date('short') ~ ' - ...' }}
{% endif %}
</td>
<td class="column-small">
<ul class="record_actions">
{% if is_granted('CHILL_BUDGET_ELEMENT_SEE', f) %}
<li>
<a href="{{ path('chill_budget_' ~ family ~ '_view', { 'id': f.id } ) }}" class="btn btn-sm btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_BUDGET_ELEMENT_UPDATE', f) %}
<li>
<a href="{{ path('chill_budget_' ~ family ~'_edit', { 'id': f.id } ) }}" class="btn btn-sm btn-edit"></a>
</li>
{% endif %}
{% if is_granted('CHILL_BUDGET_ELEMENT_DELETE', f) %}
<li>
<a href="{{ path('chill_budget_' ~ family ~ '_delete', { 'id': f.id } ) }}" class="btn btn-sm btn-delete"></a>
</li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
<tr class="total">
<td class="el-type">
{{ 'Total'|trans }}
</td>
<td>
{{ total|format_currency('EUR') }}
</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
{% endmacro %}
{% macro table_results(actualCharges, actualResources) %}
{% set totalCharges = 0 %}
{% for c in actualCharges %}
{% set totalCharges = totalCharges + c.amount %}
{% endfor %}
{% set totalResources = 0 %}
{% for r in actualResources %}
{% set totalResources = totalResources + r.amount %}
{% endfor %}
{% set result = (totalResources - totalCharges) %}
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>{{ 'Budget calculator result'|trans }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ 'The balance'|trans }}</td>
<td>&nbsp;</td>
<td>
{{ result|format_currency('EUR') }}
</td>
</tr>
</tbody>
</table>
{% endmacro %}

View File

@ -0,0 +1,53 @@
{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_elements, table_results %}
<div class="accordion" id="past_{{ entity.id }}">
<div class="accordion-item">
<h2 class="accordion-header" id="heading_past_{{ entity.id }}">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse_past_{{ entity.id }}"
aria-expanded="false"
aria-controls="collapse_past_{{ entity.id }}">
<span class="folded">{{ 'Show past budget'|trans }}</span>
<span class="unfolded text-secondary">{{ 'Hide budget'|trans }}</span>
</button>
</h2>
<div id="collapse_past_{{ entity.id }}"
class="accordion-collapse collapse"
aria-labelledby="heading_past_{{ entity.id }}"
data-bs-parent="#past_{{ entity.id }}">
<div class="flex-table">
<h3 class="family-title">{{ 'Past resources'|trans }}</h3>
{% if pastResources|length > 0 %}
<div class="item-bloc">
{{ table_elements(pastResources, 'resource') }}
</div>
{% else %}
<div class="item-bloc">
<span class="chill-no-data-statement">{{ 'No past resources registered'|trans }}</span>
</div>
{% endif %}
</div>
<div class="flex-table">
<h3 class="family-title">{{ 'Past charges'|trans }}</h3>
{% if pastCharges|length > 0 %}
<div class="item-bloc">
{{ table_elements(pastCharges, 'charge') }}
</div>
{% else %}
<div class="item-bloc">
<span class="chill-no-data-statement">{{ 'No past charges registered'|trans }}</span>
</div>
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -1,18 +1,29 @@
{% extends "@ChillPerson/Person/layout.html.twig" %} {% if element.person is not null %}
{% set template = '@ChillPerson/Person/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_index' %}
{% set activeRouteKey = '' %}
{% set person = element.person %}
{% set confirm_question = 'Are you sure you want to remove the charge "%type%" associated to "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname, '%type%': element.type|budget_element_type_display('charge') } ) %}
{% else %}
{% set template = '@ChillPerson/Household/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_household_index' %}
{% set activeRouteKey = '' %}
{% set household = element.household %}
{% set confirm_question = 'Are you sure you want to remove the charge "%type%" associated to household "%household%" ?'|trans({ '%household%' : household.id, '%type%': element.type|budget_element_type_display('charge') } ) %}
{% endif %}
{% set activeRouteKey = '' %} {% extends template %}
{% set person = element.person %}
{% block title 'Remove resource'|trans %} {% block title 'Remove charge'|trans %}
{% block personcontent %} {% block content %}
{{ include('ChillMainBundle:Util:confirmation_template.html.twig', {{ include('ChillMainBundle:Util:confirmation_template.html.twig',
{ {
'title' : 'Remove charge'|trans, 'title' : 'Remove charge'|trans,
'confirm_question' : 'Are you sure you want to remove the charge "%type%" associated to "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname, '%type%': element.type|budget_element_type_display('charge') } ), 'confirm_question' : confirm_question,
'cancel_route' : 'chill_budget_elements_index', 'cancel_route' : indexPage,
'cancel_parameters' : { 'id' : element.person.id }, 'cancel_parameters' : { 'id': person is defined ? person.id : household.id },
'form' : delete_form 'form' : delete_form
} ) }} } ) }}

View File

@ -1,31 +1,43 @@
{% extends "@ChillPerson/Person/layout.html.twig" %} {% if element.person is not null %}
{% set template = '@ChillPerson/Person/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_index' %}
{% set activeRouteKey = '' %}
{% set person = element.person %}
{% else %}
{% set template = '@ChillPerson/Household/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_household_index' %}
{% set activeRouteKey = '' %}
{% set household = element.household %}
{% endif %}
{% set title = 'Edit charge'|trans %}
{% extends template %}
{% set activeRouteKey = '' %}
{% set title = 'Edit Charge for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %}
{% block title title %} {% block title title %}
{% block personcontent %} {% block content %}
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
{{ form_start(form) }} {{ form_start(form) }}
{{ form_row(form.type) }} {{ form_row(form.type) }}
{{ form_row(form.amount) }} {{ form_row(form.amount) }}
{{ form_row(form.help) }} {{ form_row(form.help) }}
{{ form_row(form.comment) }} {{ form_row(form.comment) }}
{{ form_row(form.startDate) }} {{ form_row(form.startDate) }}
{{ form_row(form.endDate) }} {{ form_row(form.endDate) }}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path("chill_budget_elements_index", { 'id': person.id } ) }}" class="btn btn-cancel"> <a href="{{ path(indexPage, { 'id': person is defined ? person.id : household.id } ) }}" class="btn btn-cancel">
{{ 'Back to the list'|trans }} {{ 'Back to the list'|trans }}
</a> </a>
</li> </li>
<li> <li>
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-create' }, 'label': 'Edit' } ) }} {{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-edit' }, 'label': 'Edit' } ) }}
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}
{% endblock %} {% endblock %}

View File

@ -1,31 +1,42 @@
{% extends "@ChillPerson/Person/layout.html.twig" %} {% if element.person is not null %}
{% set template = '@ChillPerson/Person/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_index' %}
{% set activeRouteKey = '' %}
{% set person = element.person %}
{% else %}
{% set template = '@ChillPerson/Household/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_household_index' %}
{% set activeRouteKey = '' %}
{% set household = element.household %}
{% endif %}
{% set activeRouteKey = '' %} {% set title = 'New charge'|trans %}
{% set title = 'New Charge for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %}
{% extends template %}
{% block title title %} {% block title title %}
{% block personcontent %} {% block content %}
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
{{ form_start(form) }} {{ form_start(form) }}
{{ form_row(form.type) }} {{ form_row(form.type) }}
{{ form_row(form.amount) }} {{ form_row(form.amount) }}
{{ form_row(form.help) }} {{ form_row(form.help) }}
{{ form_row(form.comment) }} {{ form_row(form.comment) }}
{{ form_row(form.startDate) }} {{ form_row(form.startDate) }}
{{ form_row(form.endDate) }} {{ form_row(form.endDate) }}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path("chill_budget_elements_index", { 'id': person.id } ) }}" class="btn btn-cancel"> <a href="{{ path(indexPage, { 'id': person is defined ? person.id : household.id } ) }}" class="btn btn-cancel">
{{ 'Back to the list'|trans }} {{ 'Back to the list'|trans }}
</a> </a>
</li> </li>
<li> <li>
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-create' }, 'label': 'Create' } ) }} {{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-create' }, 'label': 'Create' } ) }}
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}
{% endblock %} {% endblock %}

View File

@ -1,49 +1,68 @@
{% extends "@ChillPerson/Person/layout.html.twig" %} {% if element.person is not null %}
{% set template = '@ChillPerson/Person/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_index' %}
{% set activeRouteKey = '' %}
{% set person = element.person %}
{% else %}
{% set template = '@ChillPerson/Household/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_household_index' %}
{% set activeRouteKey = '' %}
{% set household = element.household %}
{% endif %}
{% set activeRouteKey = '' %} {% set title = 'Charge' %}
{% set person = element.person %}
{% set title = 'Charge for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %} {% extends template %}
{% block title title %} {% block title title %}
{% block personcontent %} {% block content %}
<h1>{{ title }}</h1> <div class="budget-show">
<h1>{{ title }}</h1>
<dl class="chill_view_data"> <div class="flex-table">
<dt>{{ 'Type'|trans }}</dt> <div class="item-bloc">
<dd>{{ element.type|budget_element_type_display('charge') }}</dd> <div class="item-row">
<h2 class="badge-title">
<dt>{{ 'Amount'|trans }}</dt> <span class="title_label title_label_charge"></span>
<dd>{{ element.amount|localizedcurrency('EUR') }}</dd> <span class="title_action">{{ element.type|budget_element_type_display('charge') }}</span>
</h2>
<dt>{{ 'Validity period'|trans }}</dt> </div>
<dd> <div class="item-row separator">
{% if element.endDate is not null %} <dl class="chill_view_data">
{{ 'Valid since %startDate% until %endDate%'|trans( { '%startDate%': element.startDate|localizeddate('long', 'none'), '%endDate%': familyMember.endDate|localizeddate('long', 'none') } ) }} <dt>{{ 'Amount'|trans }}</dt>
{% else %} <dd>{{ element.amount|format_currency('EUR') }}</dd>
{{ 'Valid since %startDate%'|trans( { '%startDate%': element.startDate|localizeddate('long', 'none') } ) }} <dt>{{ 'Validity period'|trans }}</dt>
{% endif %} <dd>
</dd> {% if element.endDate is not null %}
{{ 'Valid since %startDate% until %endDate%'|trans( { '%startDate%': element.startDate|format_date('long'), '%endDate%': element.endDate|format_date('long') } ) }}
<dt>{{ 'Comment'|trans }}</dt> {% else %}
<dd> {{ 'Valid since %startDate%'|trans( { '%startDate%': element.startDate|format_date('long') } ) }}
{%- if element.comment is not empty -%} {% endif %}
<blockquote class="chill-user-quote"> </dd>
{{ element.comment }} <dt>{{ 'Comment'|trans }}</dt>
</blockquote> <dd>
{%- else -%} {%- if element.comment is not empty -%}
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <blockquote class="chill-user-quote">
{%- endif -%} {{ element.comment }}
</dd> </blockquote>
</dl> {%- else -%}
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
{%- endif -%}
</dd>
</dl>
</div>
</div>
</div>
</div>
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path("chill_budget_elements_index", { 'id': person.id } ) }}" class="btn btn-cancel"> <a href="{{ path(indexPage, { 'id': person is defined ? person.id : household.id } ) }}" class="btn btn-cancel">
{{ 'Back to the list'|trans }} {{ 'Back to the list'|trans }}
</a> </a>
</li> </li>
{% if is_granted(constant('Chill\\AMLI\\BudgetBundle\\Security\\Authorization\\BudgetElementVoter::UPDATE'), element) %} {% if is_granted('CHILL_BUDGET_ELEMENT_UPDATE', element) %}
<li> <li>
<a href="{{ path('chill_budget_charge_edit', { 'id': element.id } ) }}" class="btn btn-edit">{{ 'Edit'|trans }}</a> <a href="{{ path('chill_budget_charge_edit', { 'id': element.id } ) }}" class="btn btn-edit">{{ 'Edit'|trans }}</a>
</li> </li>

View File

@ -1,228 +0,0 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = '' %}
{% set title = 'Budget for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %}
{% block title title %}
{% set actualResources = [] %}
{% set futureResources = [] %}
{% set pastResources = [] %}
{% for r in resources %}
{% if r.startDate|date('U') <= 'now'|date('U') %}
{% if r.endDate is null or r.endDate|date('U') >= 'now'|date('U') %}
{% set actualResources = actualResources|merge([ r ]) %}
{% else %}
{% set pastResources = pastResources|merge([ r ]) %}
{% endif %}
{% else %}
{% set futureResources = futureResources|merge([ r ]) %}
{% endif %}
{% endfor %}
{% set actualCharges = [] %}
{% set futureCharges = [] %}
{% set pastCharges = [] %}
{% for c in charges %}
{% if c.startDate|date('U') <= 'now'|date('U') %}
{% if c.endDate is null or c.endDate|date('U') >= 'now'|date('U') %}
{% set actualCharges = actualCharges|merge([ c ]) %}
{% else %}
{% set pastCharges = pastCharges|merge([ c ]) %}
{% endif %}
{% else %}
{% set futureCharges = futureCharges|merge([ c ]) %}
{% endif %}
{% endfor %}
{% macro table_elements(elements, family) %}
<table>
<thead>
<tr>
<th>{{ 'Budget element type'|trans }}</th>
<th>{{ 'Amount'|trans }}</th>
<th>{{ 'Validity period'|trans }}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% set total = 0 %}
{% for f in elements %}
{% set total = total + f.amount %}
<tr>
<td>
{{ f.type|budget_element_type_display(family) }}
</td>
<td>{{ f.amount|format_currency('EUR') }}</td>
<td>
{% if f.endDate is not null %}
{{ 'Valid since %startDate% until %endDate%'|trans( { '%startDate%': f.startDate|format_date('long'), '%endDate%': f.endDate|format_date('long') } ) }}
{% else %}
{{ 'Valid since %startDate%'|trans( { '%startDate%': f.startDate|format_date('long') } ) }}
{% endif %}
</td>
<td>
<ul class="record_actions">
{% if is_granted(constant('Chill\\AMLI\\BudgetBundle\\Security\\Authorization\\BudgetElementVoter::SHOW'), f) %}
<li>
<a href="{{ path('chill_budget_' ~ family ~ '_view', { 'id': f.id } ) }}" class="btn btn-show"></a>
</li>
{% endif %}
{% if is_granted(constant('Chill\\AMLI\\BudgetBundle\\Security\\Authorization\\BudgetElementVoter::UPDATE'), f) %}
<li>
<a href="{{ path('chill_budget_' ~ family ~'_edit', { 'id': f.id } ) }}" class="btn btn-edit"></a>
</li>
{% endif %}
{% if is_granted(constant('Chill\\AMLI\\BudgetBundle\\Security\\Authorization\\BudgetElementVoter::DELETE'), f) %}
<li>
<a href="{{ path('chill_budget_' ~ family ~ '_delete', { 'id': f.id } ) }}" class="btn btn-delete"></a>
</li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
<tr>
<td>
{{ 'Total'|trans }}
</td>
<td>
{{ total|format_currency('EUR') }}
</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
{% endmacro %}
{% macro table_results(results) %}
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>{{ 'Budget calculator result'|trans }}</th>
</tr>
</thead>
<tbody>
{% for result in results %}
<tr>
<td>{{ result.label }}</td>
<td>
{% if result.type == constant('CHILL\\AMLI\\BudgetBundle\\Calculator\\CalculatorResult::TYPE_CURRENCY') %}
{{ result.result|format_currency('EUR') }}
{% elseif result.type == constant('CHILL\\AMLI\\BudgetBundle\\Calculator\\CalculatorResult::TYPE_PERCENTAGE') %}
{{ result.result|round(2, 'ceil') ~ '%' }}
{% else %}
{{ result.result|round(2, 'common') }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endmacro %}
{% import _self as m %}
{% block personcontent %}
<h1>{{ title }}</h1>
<h2>{{ 'Actual budget'|trans }}</h2>
{% if resources|length == 0 and charges|length == 0 %}
<p><span class="chill-no-data-statement">{{ "There isn't any element recorded"|trans }}</span></p>
{% else %}
<h3>{{ 'Actual resources'|trans }}</h3>
{% if actualResources|length > 0 %}
{{ m.table_elements(actualResources, 'resource') }}
{% else %}
<span class="chill-no-data-statement">{{ 'No resources registered'|trans }}</span>
{% endif %}
<h3>{{ 'Actual charges'|trans }}</h3>
{% if actualCharges|length > 0 %}
{{ m.table_elements(actualCharges, 'charge') }}
{% else %}
<span class="chill-no-data-statement">{{ 'No charges registered'|trans }}</span>
{% endif %}
{% if results|length > 0 %}
<h2>{{ 'Budget calculator'|trans }}</h2>
{{ m.table_results(results) }}
{% endif %}
{% if is_granted(constant('Chill\\AMLI\\BudgetBundle\\Security\\Authorization\\BudgetElementVoter::CREATE'), person) %}
<ul class="record_actions">
<li>
<a class="btn btn-create" href="{{ path('chill_budget_resource_new', { 'id': person.id} ) }}">{{ 'Create new resource'|trans }}</a>
</li>
<li>
<a class="btn btn-create" href="{{ path('chill_budget_charge_new', { 'id': person.id} ) }}">{{ 'Create new charge'|trans }}</a>
</li>
</ul>
{% endif %}
{% endif %}
{% if pastCharges|length > 0 or pastResources|length > 0 %}
<h2>{{ 'Past budget'|trans }}</h2>
<h3>{{ 'Past resources'|trans }}</h3>
{% if pastResources|length > 0 %}
{{ m.table_elements(pastResources, 'resource') }}
{% else %}
<span class="chill-no-data-statement">{{ 'No past resources registered'|trans }}</span>
{% endif %}
<h3>{{ 'Past charges'|trans }}</h3>
{% if pastCharges|length > 0 %}
{{ m.table_elements(pastCharges, 'charge') }}
{% else %}
<span class="chill-no-data-statement">{{ 'No past charges registered'|trans }}</span>
{% endif %}
{% endif %}
{% if futureCharges|length > 0 or futureResources|length > 0 %}
<h2>{{ 'Future budget'|trans }}</h2>
<h3>{{ 'Future resources'|trans }}</h3>
{% if futureResources|length > 0 %}
{{ m.table_elements(futureResources, 'resource') }}
{% else %}
<span class="chill-no-data-statement">{{ 'No future resources registered'|trans }}</span>
{% endif %}
<h3>{{ 'Future charges'|trans }}</h3>
{% if futureCharges|length > 0 %}
{{ m.table_elements(futureCharges, 'charge') }}
{% else %}
<span class="chill-no-data-statement">{{ 'No future charges registered'|trans }}</span>
{% endif %}
{% endif %}
{% if (resources|length + charges|length) == 0 or futureCharges|length > 0 or futureResources|length > 0 or pastCharges|length > 0 or pastResources|length > 0 %}
{% if is_granted(constant('Chill\\AMLI\\BudgetBundle\\Security\\Authorization\\BudgetElementVoter::CREATE'), person) %}
<ul class="record_actions">
<li>
<a class="btn btn-create" href="{{ path('chill_budget_resource_new', { 'id': person.id} ) }}">{{ 'Create new resource'|trans }}</a>
</li>
<li>
<a class="btn btn-create" href="{{ path('chill_budget_charge_new', { 'id': person.id} ) }}">{{ 'Create new charge'|trans }}</a>
</li>
</ul>
{% endif %}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,92 @@
{% extends "@ChillPerson/Household/layout.html.twig" %}
{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_elements, table_results %}
{% set activeRouteKey = '' %}
{% set title = 'Budget for household %household%'|trans({ '%household%' : household.id } ) %}
{% block title title %}
{% block js %}
{{ encore_entry_script_tags('page_budget') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('page_budget') }}
{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{% include 'ChillBudgetBundle:Budget:_budget.html.twig' with {
'resources': resources,
'charges': charges,
'household': household
} %}
{#
<div class="flex-table">
<h3 class="family-title">{{ 'Budget calculator'|trans }}</h3>
<div class="item-bloc">
{{ table_results(wholeCharges, wholeResources) }}
</div>
</div>
#}
{% if household.getCurrentMembers|length > 0 %}
<h2 class="subtitle">{{ 'Current budget household members'|trans }}</h2>
{% for hm in household.getCurrentMembers %}
{% set member = hm.person %}
<div class="accordion" id="member_{{ member.id }}">
<div class="accordion-item">
<h2 class="accordion-header" id="heading_{{ member.id }}">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse_{{ member.id }}"
aria-expanded="false"
aria-controls="collapse_{{ member.id }}">
<span class="folded">{{ 'Show budget of %name%'|trans({'%name%': member.firstName ~ " " ~ member.lastName }) }} ({{ 'budget.number of elements'|trans({ 'nb_items': member.getBudgetResources|length + member.getBudgetResources|length }) }})</span>
<span class="unfolded text-secondary">{{ 'Hide budget of %name%'|trans({'%name%': member.firstName ~ " " ~ member.lastName }) }}</span>
</button>
</h2>
<div id="collapse_{{ member.id }}"
class="accordion-collapse collapse"
aria-labelledby="heading_{{ member.id }}"
data-bs-parent="#nonCurrent">
{% include 'ChillBudgetBundle:Budget:_budget.html.twig' with {
'resources': member.getBudgetResources,
'charges': member.getBudgetCharges,
'person': member
} %}
<ul class="record_actions">
{% if is_granted('CHILL_BUDGET_ELEMENT_SEE', member) %}
<li class="btn-budget">
<a class="btn btn-edit" title={{ 'Edit budget'|trans }} href="{{ path('chill_budget_elements_index', { 'id': member.id } ) }}"></a>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
{% endfor %}
{% endif %}
{% if is_granted('CHILL_BUDGET_ELEMENT_CREATE', household) %}
<ul class="record_actions sticky-form-buttons">
<li>
<a class="btn btn-create" href="{{ path('chill_budget_resource_household_new', { 'id': household.id} ) }}">{{ 'Create new resource'|trans }}</a>
</li>
<li>
<a class="btn btn-create" href="{{ path('chill_budget_charge_household_new', { 'id': household.id} ) }}">{{ 'Create new charge'|trans }}</a>
</li>
</ul>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,46 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_results %}
{% set activeRouteKey = '' %}
{% set title = 'Budget for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %}
{% block title title %}
{% block js %}
{{ encore_entry_script_tags('page_budget') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('page_budget') }}
{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{% include 'ChillBudgetBundle:Budget:_budget.html.twig' with {
'resources': resources,
'charges': charges,
'person': person
} %}
<div class="flex-table">
<h3 class="family-title">{{ 'Budget calculator'|trans }}</h2>
<div class="item-bloc">
{{ table_results(charges, resources) }}
</div>
</div>
{% if is_granted('CHILL_BUDGET_ELEMENT_CREATE', person) %}
<ul class="record_actions sticky-form-buttons">
<li>
<a class="btn btn-create" href="{{ path('chill_budget_resource_new', { 'id': person.id} ) }}">{{ 'Create new resource'|trans }}</a>
</li>
<li>
<a class="btn btn-create" href="{{ path('chill_budget_charge_new', { 'id': person.id} ) }}">{{ 'Create new charge'|trans }}</a>
</li>
</ul>
{% endif %}
{% endblock %}

View File

@ -1,18 +1,29 @@
{% extends "@ChillPerson/Person/layout.html.twig" %} {% if element.person is not null %}
{% set template = '@ChillPerson/Person/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_index' %}
{% set activeRouteKey = '' %}
{% set person = element.person %}
{% set confirm_question = 'Are you sure you want to remove the ressource "%type%" associated to "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname, '%type%': element.type|budget_element_type_display('resource') } ) %}
{% else %}
{% set template = '@ChillPerson/Household/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_household_index' %}
{% set activeRouteKey = '' %}
{% set household = element.household %}
{% set confirm_question = 'Are you sure you want to remove the ressource "%type%" associated to household "%household%" ?'|trans({ '%household%' : household.id, '%type%': element.type|budget_element_type_display('resource') } ) %}
{% endif %}
{% set activeRouteKey = '' %} {% extends template %}
{% set person = element.person %}
{% block title 'Remove resource'|trans %} {% block title 'Remove resource'|trans %}
{% block personcontent %} {% block content %}
{{ include('ChillMainBundle:Util:confirmation_template.html.twig', {{ include('ChillMainBundle:Util:confirmation_template.html.twig',
{ {
'title' : 'Remove resource'|trans, 'title' : 'Remove resource'|trans,
'confirm_question' : 'Are you sure you want to remove the ressource "%type%" associated to "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname, '%type%': element.type|budget_element_type_display('resource') } ), 'confirm_question' : confirm_question,
'cancel_route' : 'chill_budget_elements_index', 'cancel_route' : indexPage,
'cancel_parameters' : { 'id' : element.person.id }, 'cancel_parameters' : { 'id': person is defined ? person.id : household.id },
'form' : delete_form 'form' : delete_form
} ) }} } ) }}

View File

@ -1,10 +1,24 @@
{% extends "@ChillPerson/Person/layout.html.twig" %} {% if element.person is not null %}
{% set template = '@ChillPerson/Person/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_index' %}
{% set activeRouteKey = '' %}
{% set person = element.person %}
{% set title = 'Edit resource for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %}
{% else %}
{% set template = '@ChillPerson/Household/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_household_index' %}
{% set activeRouteKey = '' %}
{% set household = element.household %}
{% set title = 'Edit resource for household %household%'|trans({ '%household%' : household.id } ) %}
{% endif %}
{% set title = 'Edit resource'|trans %}
{% extends template %}
{% set activeRouteKey = '' %}
{% set title = 'Edit Resource for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %}
{% block title title %} {% block title title %}
{% block personcontent %} {% block content %}
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
{{ form_start(form) }} {{ form_start(form) }}
@ -17,14 +31,14 @@
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path("chill_budget_elements_index", { 'id': person.id } ) }}" class="btn btn-cancel"> <a href="{{ path(indexPage, { 'id': person is defined ? person.id : household.id } ) }}" class="btn btn-cancel">
{{ 'Back to the list'|trans }} {{ 'Back to the list'|trans }}
</a> </a>
</li> </li>
<li> <li>
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-create' }, 'label': 'Edit' } ) }} {{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-edit' }, 'label': 'Edit' } ) }}
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}
{% endblock %} {% endblock %}

View File

@ -1,10 +1,24 @@
{% extends "@ChillPerson/Person/layout.html.twig" %} {% if element.person is not null %}
{% set template = '@ChillPerson/Person/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_index' %}
{% set activeRouteKey = '' %}
{% set person = element.person %}
{% set title = 'New Resource for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %}
{% else %}
{% set template = '@ChillPerson/Household/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_household_index' %}
{% set activeRouteKey = '' %}
{% set household = element.household %}
{% set title = 'New Resource for household %household%'|trans({ '%household%' : household.id } ) %}
{% endif %}
{% set title = 'New resource'|trans %}
{% extends template %}
{% set activeRouteKey = '' %}
{% set title = 'New Resource for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %}
{% block title title %} {% block title title %}
{% block personcontent %} {% block content %}
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
{{ form_start(form) }} {{ form_start(form) }}
@ -17,14 +31,14 @@
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path("chill_budget_elements_index", { 'id': person.id } ) }}" class="btn btn-cancel"> <a href="{{ path(indexPage, { 'id': person is defined ? person.id : household.id } ) }}" class="btn btn-cancel">
{{ 'Back to the list'|trans }} {{ 'Back to the list'|trans }}
</a> </a>
</li> </li>
<li> <li>
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-create' }, 'label': 'Create' } ) }} {{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-create' }, 'label': 'Create' } ) }}
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}
{% endblock %} {% endblock %}

View File

@ -1,49 +1,68 @@
{% extends "@ChillPerson/Person/layout.html.twig" %} {% if element.person is not null %}
{% set template = '@ChillPerson/Person/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_index' %}
{% set activeRouteKey = '' %}
{% set person = element.person %}
{% else %}
{% set template = '@ChillPerson/Household/layout.html.twig' %}
{% set indexPage = 'chill_budget_elements_household_index' %}
{% set activeRouteKey = '' %}
{% set household = element.household %}
{% endif %}
{% set activeRouteKey = '' %} {% set title = 'Resource' %}
{% set person = element.person %}
{% set title = 'Resource for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %} {% extends template %}
{% block title title %} {% block title title %}
{% block personcontent %} {% block content %}
<h1>{{ title }}</h1> <div class="budget-show">
<h1>{{ title }}</h1>
<dl class="chill_view_data"> <div class="flex-table">
<dt>{{ 'Type'|trans }}</dt> <div class="item-bloc">
<dd>{{ element.type|budget_element_type_display('resource') }}</dd> <div class="item-row">
<h2 class="badge-title">
<dt>{{ 'Amount'|trans }}</dt> <span class="title_label title_label_resource"></span>
<dd>{{ element.amount|format_currency('EUR') }}</dd> <span class="title_action title_action">{{ element.type|budget_element_type_display('resource') }}</span>
</h2>
<dt>{{ 'Validity period'|trans }}</dt> </div>
<dd> <div class="item-row separator">
{% if element.endDate is not null %} <dl class="chill_view_data">
{{ 'Valid since %startDate% until %endDate%'|trans( { '%startDate%': element.startDate|format_date('long'), '%endDate%': familyMember.endDate|format_date('long') } ) }} <dt class="inline">{{ 'Amount'|trans }}</dt>
{% else %} <dd>{{ element.amount|format_currency('EUR') }}</dd>
{{ 'Valid since %startDate%'|trans( { '%startDate%': element.startDate|format_date('long') } ) }} <dt class="inline">{{ 'Validity period'|trans }}</dt>
{% endif %} <dd>
</dd> {% if element.endDate is not null %}
{{ 'Valid since %startDate% until %endDate%'|trans( { '%startDate%': element.startDate|format_date('long'), '%endDate%': element.endDate|format_date('long') } ) }}
<dt>{{ 'Comment'|trans }}</dt> {% else %}
<dd> {{ 'Valid since %startDate%'|trans( { '%startDate%': element.startDate|format_date('long') } ) }}
{%- if element.comment is not empty -%} {% endif %}
<blockquote class="chill-user-quote"> </dd>
{{ element.comment }} <dt class="inline">{{ 'Comment'|trans }}</dt>
</blockquote> <dd>
{%- else -%} {%- if element.comment is not empty -%}
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <blockquote class="chill-user-quote">
{%- endif -%} {{ element.comment }}
</dd> </blockquote>
</dl> {%- else -%}
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
{%- endif -%}
</dd>
</dl>
</div>
</div>
</div>
</div>
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path("chill_budget_elements_index", { 'id': person.id } ) }}" class="btn btn-cancel"> <a href="{{ path(indexPage, { 'id': person is defined ? person.id : household.id } ) }}" class="btn btn-cancel">
{{ 'Back to the list'|trans }} {{ 'Back to the list'|trans }}
</a> </a>
</li> </li>
{% if is_granted(constant('Chill\\AMLI\\BudgetBundle\\Security\\Authorization\\BudgetElementVoter::UPDATE'), element) %} {% if is_granted('CHILL_BUDGET_ELEMENT_UPDATE', element) %}
<li> <li>
<a href="{{ path('chill_budget_resource_edit', { 'id': element.id } ) }}" class="btn btn-edit">{{ 'Edit'|trans }}</a> <a href="{{ path('chill_budget_resource_edit', { 'id': element.id } ) }}" class="btn btn-edit">{{ 'Edit'|trans }}</a>
</li> </li>

View File

@ -9,15 +9,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Security\Authorization; namespace Chill\BudgetBundle\Security\Authorization;
use Chill\AMLI\BudgetBundle\Entity\AbstractElement; use Chill\BudgetBundle\Entity\AbstractElement;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter; use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use function in_array; use function in_array;
@ -30,22 +31,24 @@ class BudgetElementVoter extends AbstractChillVoter implements ProvideRoleHierar
public const ROLES = [ public const ROLES = [
self::CREATE, self::CREATE,
self::DELETE, self::DELETE,
self::SHOW, self::SEE,
self::UPDATE, self::UPDATE,
]; ];
public const SHOW = 'CHILL_BUDGET_ELEMENT_SHOW'; public const SEE = 'CHILL_BUDGET_ELEMENT_SEE';
public const UPDATE = 'CHILL_BUDGET_ELEMENT_UPDATE'; public const UPDATE = 'CHILL_BUDGET_ELEMENT_UPDATE';
/** protected VoterHelperInterface $voter;
* @var AuthorizationHelper
*/
protected $authorizationHelper;
public function __construct(AuthorizationHelper $authorizationHelper) public function __construct(VoterHelperFactoryInterface $voterFactory)
{ {
$this->authorizationHelper = $authorizationHelper; $this->voter = $voterFactory
->generate(self::class)
->addCheckFor(AbstractElement::class, self::ROLES)
->addCheckFor(Person::class, [self::CREATE, self::SEE])
->addCheckFor(Household::class, [self::CREATE, self::SEE])
->build();
} }
public function getRoles(): array public function getRoles(): array
@ -66,18 +69,11 @@ class BudgetElementVoter extends AbstractChillVoter implements ProvideRoleHierar
protected function supports($attribute, $subject) protected function supports($attribute, $subject)
{ {
return (in_array($attribute, self::ROLES, true) && $subject instanceof AbstractElement) return (in_array($attribute, self::ROLES, true) && $subject instanceof AbstractElement)
|| ($subject instanceof Person && in_array($attribute, [self::SHOW, self::CREATE], true)); || (($subject instanceof Person || $subject instanceof Household) && in_array($attribute, [self::SEE, self::CREATE], true));
} }
protected function voteOnAttribute($attribute, $subject, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token) protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{ {
$user = $token->getUser(); return $this->voter->voteOnAttribute($attribute, $subject, $token);
if (false === $user instanceof User) {
return false;
}
return $this->authorizationHelper
->userHasAccess($user, $subject, new Role($attribute));
} }
} }

View File

@ -0,0 +1,168 @@
<?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\BudgetBundle\Service\Summary;
use Chill\BudgetBundle\Config\ConfigRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMapping;
use LogicException;
use function count;
/**
* Helps to find a summary of the budget: the sum of resources and charges.
*/
class SummaryBudget implements SummaryBudgetInterface
{
private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
private array $chargeLabels;
private ConfigRepository $configRepository;
private EntityManagerInterface $em;
private array $resourcesLabels;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(EntityManagerInterface $em, ConfigRepository $configRepository, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->em = $em;
$this->configRepository = $configRepository;
$this->chargeLabels = $configRepository->getChargesLabels();
$this->resourcesLabels = $configRepository->getResourcesLabels();
$this->translatableStringHelper = $translatableStringHelper;
}
public function getSummaryForHousehold(?Household $household): array
{
if (null === $household) {
return [
'resources' => $this->getEmptyResourceArray(),
'charges' => $this->getEmptyChargeArray(),
];
}
$personIds = $household->getCurrentPersons()->map(static function (Person $p) { return $p->getId(); });
$ids = implode(', ', array_fill(0, count($personIds), '?'));
$parameters = [...$personIds, $household->getId()];
$rsm = $this->buildRsm();
$resources = $this->em->createNativeQuery(strtr(self::QUERY_RESOURCE_BY_HOUSEHOLD, ['_ids_' => $ids]), $rsm)
->setParameters($parameters)
->getResult();
$charges = $this->em->createNativeQuery(strtr(self::QUERY_CHARGE_BY_HOUSEHOLD, ['_ids_' => $ids]), $rsm)
->setParameters($parameters)
->getResult();
return [
'resources' => array_merge($this->getEmptyResourceArray(), $this->rowToArray($resources, 'resource')),
'charges' => array_merge($this->getEmptyChargeArray(), $this->rowToArray($charges, 'charge')),
];
}
public function getSummaryForPerson(?Person $person): array
{
if (null === $person) {
return [
'resources' => $this->getEmptyResourceArray(),
'charges' => $this->getEmptyChargeArray(),
];
}
$rsm = $this->buildRsm();
$resources = $this->em->createNativeQuery(self::QUERY_RESOURCE_BY_PERSON, $rsm)
->setParameters([$person->getId()])
->getResult();
$charges = $this->em->createNativeQuery(self::QUERY_CHARGE_BY_PERSON, $rsm)
->setParameters([$person->getId()])
->getResult();
return [
'resources' => array_merge($this->getEmptyResourceArray(), $this->rowToArray($resources, 'resource')),
'charges' => array_merge($this->getEmptyChargeArray(), $this->rowToArray($charges, 'charge')),
];
}
private function buildRsm(): ResultSetMapping
{
$rsm = new ResultSetMapping();
$rsm
->addScalarResult('sum', 'sum')
->addScalarResult('type', 'type')
->addScalarResult('comment', 'comment');
return $rsm;
}
private function getEmptyChargeArray(): array
{
$keys = $this->configRepository->getChargesKeys();
$labels = $this->chargeLabels;
return array_combine($keys, array_map(function ($i) use ($labels) {
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => ''];
}, $keys));
}
private function getEmptyResourceArray(): array
{
$keys = $this->configRepository->getResourcesKeys();
$labels = $this->resourcesLabels;
return array_combine($keys, array_map(function ($i) use ($labels) {
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => ''];
}, $keys));
}
private function rowToArray(array $rows, string $kind): array
{
switch ($kind) {
case 'charge':
$label = $this->chargeLabels;
break;
case 'resource':
$label = $this->resourcesLabels;
break;
default:
throw new LogicException();
}
$result = [];
foreach ($rows as $row) {
$result[$row['type']] = [
'sum' => (float) $row['sum'],
'label' => $this->translatableStringHelper->localize($label[$row['type']]),
'comment' => (string) $row['comment'],
];
}
return $result;
}
}

View File

@ -0,0 +1,25 @@
<?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\BudgetBundle\Service\Summary;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
/**
* Helps to find a summary of the budget: the sum of resources and charges.
*/
interface SummaryBudgetInterface
{
public function getSummaryForHousehold(?Household $household): array;
public function getSummaryForPerson(?Person $person): array;
}

View File

@ -9,9 +9,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Templating; namespace Chill\BudgetBundle\Templating;
use Chill\AMLI\BudgetBundle\Config\ConfigRepository; use Chill\BudgetBundle\Config\ConfigRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
use Twig\TwigFilter; use Twig\TwigFilter;

View File

@ -9,7 +9,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\AMLI\BudgetBundle\Tests\Controller; namespace Chill\BudgetBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

View File

@ -0,0 +1,9 @@
// this file loads all assets from the Chill budget bundle
module.exports = function(encore, entries)
{
encore.addAliases({
ChillBudgetAssets: __dirname + '/Resources/public'
});
encore.addEntry('page_budget', __dirname + '/Resources/public/page/index.js');
};

View File

@ -6,7 +6,7 @@
"keywords" : ["chill", "social work"], "keywords" : ["chill", "social work"],
"homepage" : "https://framagit.org/Chill-project/BudgetBundle", "homepage" : "https://framagit.org/Chill-project/BudgetBundle",
"autoload": { "autoload": {
"psr-4": { "Chill\\AMLI\\BudgetBundle\\": "" } "psr-4": { "Chill\\BudgetBundle\\": "" }
}, },
"autoload-dev": { "autoload-dev": {
"classmap": [ "Resources/test/Fixtures/App/app/AppKernel.php" ] "classmap": [ "Resources/test/Fixtures/App/app/AppKernel.php" ]

View File

@ -1,3 +1,3 @@
chill_amli_budget_controllers: chill_budget_controllers:
resource: "@ChillAMLIBudgetBundle/Controller" resource: "@ChillBudgetBundle/Controller"
type: annotation type: annotation

View File

@ -1,2 +1,2 @@
services: services:
Chill\AMLI\BudgetBundle\Calculator\CalculatorManager: ~ Chill\BudgetBundle\Calculator\CalculatorManager: ~

View File

@ -1,5 +1,5 @@
services: services:
Chill\AMLI\BudgetBundle\Config\ConfigRepository: Chill\BudgetBundle\Config\ConfigRepository:
arguments: arguments:
$resources: '%chill_budget.resources%' $resources: '%chill_budget.resources%'
$charges: '%chill_budget.charges%' $charges: '%chill_budget.charges%'

View File

@ -1,5 +1,5 @@
services: services:
Chill\AMLI\BudgetBundle\Controller\: Chill\BudgetBundle\Controller\:
autowire: true autowire: true
resource: '../../Controller' resource: '../../Controller'
tags: ['controller.service_arguments'] tags: ['controller.service_arguments']

View File

@ -1,14 +1,14 @@
services: services:
Chill\AMLI\BudgetBundle\Form\ResourceType: Chill\BudgetBundle\Form\ResourceType:
arguments: arguments:
$configRepository: '@Chill\AMLI\BudgetBundle\Config\ConfigRepository' $configRepository: '@Chill\BudgetBundle\Config\ConfigRepository'
$translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
tags: tags:
- { name: 'form.type' } - { name: 'form.type' }
Chill\AMLI\BudgetBundle\Form\ChargeType: Chill\BudgetBundle\Form\ChargeType:
arguments: arguments:
$configRepository: '@Chill\AMLI\BudgetBundle\Config\ConfigRepository' $configRepository: '@Chill\BudgetBundle\Config\ConfigRepository'
$translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
tags: tags:
- { name: 'form.type' } - { name: 'form.type' }

View File

@ -1,7 +1,8 @@
services: services:
Chill\AMLI\BudgetBundle\Menu\UserMenuBuilder: Chill\BudgetBundle\Menu\PersonMenuBuilder:
arguments: autowire: true
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' autoconfigure: true
$translator: '@Symfony\Contracts\Translation\TranslatorInterface'
tags: Chill\BudgetBundle\Menu\HouseholdMenuBuilder:
- { name: 'chill.menu_builder' } autowire: true
autoconfigure: true

View File

@ -1,7 +1,6 @@
services: services:
Chill\AMLI\BudgetBundle\Security\Authorization\BudgetElementVoter: Chill\BudgetBundle\Security\Authorization\BudgetElementVoter:
arguments: autowire: true
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
tags: tags:
- { name: chill.role } - { name: chill.role }
- { name: security.voter } - { name: security.voter }

View File

@ -0,0 +1,6 @@
services:
Chill\BudgetBundle\Service\:
resource: './../Service'
autowire: true
autoconfigure: true

View File

@ -1,7 +1,7 @@
services: services:
Chill\AMLI\BudgetBundle\Templating\Twig: Chill\BudgetBundle\Templating\Twig:
arguments: arguments:
$configRepository: '@Chill\AMLI\BudgetBundle\Config\ConfigRepository' $configRepository: '@Chill\BudgetBundle\Config\ConfigRepository'
$translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
tags: tags:
- { name: 'twig.extension' } - { name: 'twig.extension' }

View File

@ -9,29 +9,29 @@
declare(strict_types=1); declare(strict_types=1);
namespace Application\Migrations; namespace Chill\Migrations\Budget;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/** /**
* create schema for chill budget. * create schema for chill budget.
*/ */
final class Version20180522080432 extends AbstractMigration final class Version20180522080432 extends AbstractMigration
{ {
public function down(Schema $schema) public function down(Schema $schema): void
{ {
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('DROP SCHEMA chill_budget CASCADE');
$this->addSql('CREATE SCHEMA chill_budget CASCADE');
} }
public function up(Schema $schema) public function getDescription(): string
{ {
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); return 'Creation of necessary tables for budget bundle';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE SCHEMA chill_budget'); $this->addSql('CREATE SCHEMA chill_budget');
$this->addSql('DROP SEQUENCE report_id_seq CASCADE');
$this->addSql('CREATE SEQUENCE chill_budget.resource_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_budget.resource_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_budget.charge_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_budget.charge_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_budget.resource (id INT NOT NULL, person_id INT DEFAULT NULL, type VARCHAR(255) NOT NULL, amount NUMERIC(10, 2) NOT NULL, comment TEXT DEFAULT NULL, startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, endDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_budget.resource (id INT NOT NULL, person_id INT DEFAULT NULL, type VARCHAR(255) NOT NULL, amount NUMERIC(10, 2) NOT NULL, comment TEXT DEFAULT NULL, startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, endDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');

View File

@ -9,7 +9,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace Application\Migrations; namespace Chill\Migrations\Budget;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
@ -21,15 +21,11 @@ final class Version20181219145631 extends AbstractMigration
{ {
public function down(Schema $schema): void public function down(Schema $schema): void
{ {
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_budget.charge ALTER help SET NOT NULL'); $this->addSql('ALTER TABLE chill_budget.charge ALTER help SET NOT NULL');
} }
public function up(Schema $schema): void public function up(Schema $schema): void
{ {
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_budget.charge ALTER help DROP NOT NULL'); $this->addSql('ALTER TABLE chill_budget.charge ALTER help DROP NOT NULL');
} }
} }

View File

@ -0,0 +1,44 @@
<?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\Migrations\Budget;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220224090319 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_budget.charge DROP CONSTRAINT FK_5C99D2C3E79FF843');
$this->addSql('ALTER TABLE chill_budget.charge DROP household_id');
$this->addSql('ALTER TABLE chill_budget.resource DROP CONSTRAINT FK_5E0A5E97E79FF843');
$this->addSql('ALTER TABLE chill_budget.resource DROP household_id');
}
public function getDescription(): string
{
return 'Add household to budget AbstractElement';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_budget.charge ADD household_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_budget.charge ADD CONSTRAINT FK_5C99D2C3E79FF843 FOREIGN KEY (household_id) REFERENCES chill_person_household (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_5C99D2C3E79FF843 ON chill_budget.charge (household_id)');
$this->addSql('ALTER TABLE chill_budget.resource ADD household_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_budget.resource ADD CONSTRAINT FK_5E0A5E97E79FF843 FOREIGN KEY (household_id) REFERENCES chill_person_household (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_5E0A5E97E79FF843 ON chill_budget.resource (household_id)');
}
}

View File

@ -0,0 +1,8 @@
budget:
number of elements: >-
{nb_items, plural,
=0 {Aucun élément}
one {Un élément}
many {# éléments}
other {# éléments}
}

View File

@ -1,12 +1,24 @@
Budget: Budget Budget: Budget
Resource: Ressource
Charge: Charge
Budget for %name%: Budget de %name% Budget for %name%: Budget de %name%
Budget for household %household%: Budget du ménage
Current budget household members: Budget actuel des membres du ménage
Show budget of %name%: Montrer budget de %name%
See complete budget: Voir budget complet
Hide budget: Masquer
Hide budget of %name%: Masquer budget de %name%
Resource element type: Nature de la ressource Resource element type: Nature de la ressource
Actual budget: Budget actuel Actual budget: Éléments actuels du budget
Actual resources: Ressources actuelles Actual resources: Ressources actuelles
Actual resources for %name%: Ressources actuelles de %name%
Actual charges for %name%: Charges actuelles de %name%
Actual charges: Charges actuelles Actual charges: Charges actuelles
Past budget: Éléments du budget passé Past budget: Éléments du budget passé
Show past budget: Montrer budget passé
Show future budget: Montrer budget future
Past resources: Ressources passées Past resources: Ressources passées
Past charges: Chargées passées Past charges: Charges passées
Future budget: Futurs éléments du budget Future budget: Futurs éléments du budget
Future resources: Ressources futures Future resources: Ressources futures
Future charges: Charges futures Future charges: Charges futures
@ -17,6 +29,7 @@ End of validity period: Fin de la période de validité
Total: Total Total: Total
Create new resource: Créer une nouvelle ressource Create new resource: Créer une nouvelle ressource
Create new charge: Créer une nouvelle charge Create new charge: Créer une nouvelle charge
See person: Voir personne
There isn't any element recorded: Aucun élément enregistré There isn't any element recorded: Aucun élément enregistré
No resources registered: Aucune ressource enregistrée No resources registered: Aucune ressource enregistrée
@ -25,11 +38,13 @@ No past resources registered: Aucune ressource passée
No past charges registered: Aucune charge passée No past charges registered: Aucune charge passée
No future resources registered: Aucune ressource future enregistrée No future resources registered: Aucune ressource future enregistrée
No future charges registered: Aucune ressource future enregistrée No future charges registered: Aucune ressource future enregistrée
No current budget element registered: Pas des éléments de budget actuelles enregistrés
New Resource for %name%: Nouvelle ressource pour %name% New resource: Nouvelle ressource
New Charge for %name%: Nouvelle charge pour %name% New charge: Nouvelle charge
Edit Resource for %name%: Modifier une ressource de %name% Edit resource: Modifier une ressource
Edit Charge for %name%: Modifier une charge de %name% Edit: Modifier
Edit charge: Modifier une charge
Remove resource: Supprimer la ressource Remove resource: Supprimer la ressource
Remove charge: Supprimer la charge Remove charge: Supprimer la charge
Are you sure you want to remove the ressource "%type%" associated to "%name%" ?: Êtes-vous sûr·e de vouloir supprimer la ressource de nature "%type%" associée à %name% ? Are you sure you want to remove the ressource "%type%" associated to "%name%" ?: Êtes-vous sûr·e de vouloir supprimer la ressource de nature "%type%" associée à %name% ?
@ -54,7 +69,8 @@ charge.help.yes: Oui
charge.help.not-concerned: Non concerné charge.help.not-concerned: Non concerné
Budget calculator: Calculs et indices sur le budget Budget calculator: Calculs et indices sur le budget
Budget calculator result: Résultats Budget calculator result: Résultats
The balance: Différence entre ressources et charges
'Valid since %startDate% until %endDate%': Valide depuis le %startDate% jusqu'au %endDate% Valid since %startDate% until %endDate%: Valide depuis le %startDate% jusqu'au %endDate%
'Valid since %startDate%': Valide depuis le %startDate% Valid since %startDate%: Valide depuis le %startDate%

View File

@ -41,12 +41,14 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
$period = $parameters['accompanyingCourse']; $period = $parameters['accompanyingCourse'];
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) { if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) {
/*
$menu->addChild($this->translator->trans('Calendar'), [ $menu->addChild($this->translator->trans('Calendar'), [
'route' => 'chill_calendar_calendar_list', 'route' => 'chill_calendar_calendar_list',
'routeParameters' => [ 'routeParameters' => [
'accompanying_period_id' => $period->getId(), 'accompanying_period_id' => $period->getId(),
], ]) ], ])
->setExtras(['order' => 35]); ->setExtras(['order' => 35]);
*/
} }
} }

View File

@ -25,7 +25,7 @@
{% endblock %} {% endblock %}
{% block layout_wvm_content %} {% block layout_wvm_content %}
{% block admin_content %}<!-- block personcontent empty --> {% block admin_content %}<!-- block content empty -->
<h1>{{ 'CustomFields configuration' |trans }}</h1> <h1>{{ 'CustomFields configuration' |trans }}</h1>
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}

View File

@ -26,6 +26,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Exception; use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
@ -221,14 +222,14 @@ final class DocGeneratorTemplateController extends AbstractController
$builder = $this->createFormBuilder( $builder = $this->createFormBuilder(
array_merge( array_merge(
$context->getFormData($template, $entity), $context->getFormData($template, $entity),
$isTest ? ['test_file' => null] : [] $isTest ? ['test_file' => null, 'show_data' => false] : []
) )
); );
$context->buildPublicForm($builder, $template, $entity); $context->buildPublicForm($builder, $template, $entity);
} else { } else {
$builder = $this->createFormBuilder( $builder = $this->createFormBuilder(
['test_file' => null] ['test_file' => null, 'show_data' => false]
); );
} }
@ -237,6 +238,10 @@ final class DocGeneratorTemplateController extends AbstractController
'label' => 'Template file', 'label' => 'Template file',
'required' => false, 'required' => false,
]); ]);
$builder->add('show_data', CheckboxType::class, [
'label' => 'Show data instead of generating',
'required' => false,
]);
} }
$form = $builder->getForm()->handleRequest($request); $form = $builder->getForm()->handleRequest($request);
@ -266,6 +271,11 @@ final class DocGeneratorTemplateController extends AbstractController
} }
} }
if ($isTest && isset($form) && $form['show_data']->getData()) {
// very ugly hack...
dd($datas);
}
try { try {
$generatedResource = $this $generatedResource = $this
->driver ->driver

View File

@ -155,7 +155,7 @@ class DocumentAccompanyingCourseController extends AbstractController
$documents = $this->courseRepository $documents = $this->courseRepository
->findBy( ->findBy(
['course' => $course], ['course' => $course],
['date' => 'DESC'], ['date' => 'DESC', 'id' => 'DESC'],
$pagination->getItemsPerPage(), $pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber() $pagination->getCurrentPageFirstItemNumber()
); );

View File

@ -178,7 +178,7 @@ class DocumentPersonController extends AbstractController
$documents = $this->personDocumentACLAwareRepository->findByPerson( $documents = $this->personDocumentACLAwareRepository->findByPerson(
$person, $person,
[], ['date' => 'DESC', 'id' => 'DESC'],
$pagination->getItemsPerPage(), $pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber() $pagination->getCurrentPageFirstItemNumber()
); );

View File

@ -24,22 +24,15 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt
{ {
/** /**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person") * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person")
*
* @var Person
*/ */
private $person; private Person $person;
public function getCenter() public function getCenter()
{ {
return $this->getPerson()->getCenter(); return $this->getPerson()->getCenter();
} }
/** public function getPerson(): Person
* Get person.
*
* @return \Chill\MainBundle\Entity\Person
*/
public function getPerson()
{ {
return $this->person; return $this->person;
} }

View File

@ -61,13 +61,13 @@ class StoredObject implements AsyncFileInterface, Document
/** /**
* @var int[] * @var int[]
* @ORM\Column(type="json", name="iv") * @ORM\Column(type="json", name="iv")
* @Serializer\Groups({"write"}) * @Serializer\Groups({"read", "write"})
*/ */
private array $iv = []; private array $iv = [];
/** /**
* @ORM\Column(type="json", name="key") * @ORM\Column(type="json", name="key")
* @Serializer\Groups({"write"}) * @Serializer\Groups({"read", "write"})
*/ */
private array $keyInfos = []; private array $keyInfos = [];

View File

@ -64,7 +64,7 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito
$this->addACL($qb, $person); $this->addACL($qb, $person);
foreach ($orderBy as [$field, $order]) { foreach ($orderBy as $field => $order) {
$qb->addOrderBy($field, $order); $qb->addOrderBy($field, $order);
} }

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