diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e2691c63..0d11c9ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,21 @@ and this project adheres to ## Unreleased +* add 3 new fields to PostalCode and adapt postal code command and fixtures +* [Aside activity] Fixes for aside activity + * categories with child + * fast creation buttons + * add ordering for types + +* [ThirdParty]: + + * third party list + * create a kind contact/institution when create a new thirdparty, and set contact embedded as kind=child; + * filter thirdparties in list + +* [FilterOrder]: add development kit for generating filter and ordering in list ## Test releases @@ -26,14 +39,21 @@ and this project adheres to * a badge "requestor" is shown into search results; * periods where the person is only requestor (without participating) are also shown; - + Issues: - + * https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/13 * https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/199 * [Person form] "accept sms" not required: https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37 https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221 -* On-The-Fly modale works for showing, editing and creating person and thirdparty ; -* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ; + +* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ; +* [AccompanyingCourse Resume page] + * associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ; + * badge-title for AccompanyingCourseWork and for Activities; +* Improve badges behaviour with small screens; + +## Test release yyyy-mm-dd + diff --git a/src/Bundle/ChillActivityBundle/CHANGELOG.md b/src/Bundle/ChillActivityBundle/CHANGELOG.md index 871ead4bd..6261aa1db 100644 --- a/src/Bundle/ChillActivityBundle/CHANGELOG.md +++ b/src/Bundle/ChillActivityBundle/CHANGELOG.md @@ -27,3 +27,4 @@ Version 1.5.5 ============= - [activity] replace dropdown for selecting reasons and use chillEntity for reason rendering +- fix bug: error when trying to edit activity of which the type has been deactivated diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php index a454626b7..8a4a228eb 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php @@ -174,6 +174,10 @@ class ActivityController extends AbstractController $activityType = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityType::class) ->find($activityType_id); + if (isset($activityType) && !$activityType->isActive()) { + throw new \InvalidArgumentException('Activity type must be active'); + } + $activityData = null; if ($request->query->has('activityData')) { $activityData = $request->query->get('activityData'); diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index 8c8fb45a9..5a711408c 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -93,15 +93,13 @@ class ActivityType extends AbstractType /** @var \Chill\ActivityBundle\Entity\ActivityType $activityType */ $activityType = $options['activityType']; - if (!$activityType->isActive()) { - throw new \InvalidArgumentException('Activity type must be active'); - } - // TODO revoir la gestion des center au niveau du form des activité. if ($options['center']) { $builder->add('scope', ScopePickerType::class, [ 'center' => $options['center'], - 'role' => $options['role'] + 'role' => $options['role'], + // TODO make required again once scope and rights are fixed + 'required' => false ]); } diff --git a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss index 528d75ded..28c02e23e 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss +++ b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss @@ -1,7 +1,9 @@ // Access to Bootstrap variables and mixins @import '~ChillMainAssets/module/bootstrap/shared'; -// activity creation first step: select type page +//// ACTIVITY CREATION +// first step: select type page + div.new-activity-select-type { div.activity-row { display: flex; @@ -21,91 +23,36 @@ div.new-activity-select-type { } } -// exceptions for flex-table in list-records -div.activity-list { - div.flex-table { - div.item-bloc { - div.item-row.main { - div.item-col { - &:first-child { - flex-basis: 15%; - } - ul.list-content { - li.social-issues, li.social-actions { - .badge-primary { - font-variant: small-caps; - font-weight: bold; - font-size: 88%; - margin-bottom: 0.2em; - } - } - li.social-issues .badge-primary { - background-color: $orange; - } - li.social-actions .badge-primary { - background-color: $green; - } - } - } - } - div.item-row.comment { - margin-left: 15%; - blockquote.chill-user-quote { - margin-top: 0.5em; - margin-bottom: 0.5em; - } - } - div.item-row.details { - margin-left: 15%; +//// ACTIVITY LIST PAGE +// precise badge-title specific details - // override flex-bloc to adapt in list - // TODO refund this - div.accompanyingCourse.flex-bloc.concerned-groups { - margin: 0; - width: 100%; - justify-content: space-around; - div.item-bloc { - box-shadow: unset; - padding: 0; - flex-basis: 25%; - div.item-row { - flex-direction: column; - div.item-col { - &:first-child { - width: unset; - } - &:last-child { - border-top: 0; - margin-top: 0; - padding-top: 0; - ul.list-content { - padding: 0; - } - } - } - } - } +h2.badge-title { + div.duration { + font-size: smaller; + padding-left: 1em; + margin-top: 1em; + } + ul.list-content { + font-size: 70%; + list-style-type: none; + padding-left: 0; + margin: 0; + li { + margin-bottom: 0.2em; + // exception: change bg color for action badges above badge-title + .bg-light { + background-color: $chill-light-gray !important; } - - } - ul.list-content { - list-style-type: none; - padding-left: 1em; - margin: 0 0; - li { - margin-bottom: 0.2em; - } - } - } - div.duration { - font-size: smaller; - padding-left: 1em; - margin-top: 1em; - } - } + } + } +} +div.main { + padding: 1em; } -// exceptions for flex-bloc in concerned-groups +//// ACTIVITY SHOW AND FORM PAGES +// Exceptions for flex-bloc in concerned-groups + div.flex-bloc.concerned-groups { margin-top: 1em; div.item-bloc { @@ -130,7 +77,6 @@ div.flex-bloc.concerned-groups { } } - /// CHILL ENTITY RENDER BOX .chill-entity { diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/activity-badge-title.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/activity-badge-title.html.twig new file mode 100644 index 000000000..1e1d65d91 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/activity-badge-title.html.twig @@ -0,0 +1,88 @@ +

+ + + {% if activity.date %} +

{{ activity.date|format_date('short') }}

+ {% endif %} + +
+ {% if activity.durationTime and t.durationTimeVisible %} +

+ + {{ activity.durationTime|date('H:i') }} +

+ {% endif %} + + {% if activity.travelTime and t.travelTimeVisible %} +

+ + {{ activity.travelTime|date('H:i') }} +

+ {% endif %} +
+ +
+ + + {{ activity.type.name | localize_translatable_string }} + + + + + + +

+ +{# +{% if context == 'person' and activity.accompanyingPeriod is not empty %} +
+ + +
+{% endif %} +#} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig index 7d776b5de..89a790588 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig @@ -5,17 +5,17 @@ {% if context == 'person' %} {% set blocs = [ - { 'title': 'Others persons'|trans, + { 'title': 'Others persons'|trans, 'items': entity.persons, 'path' : 'chill_person_view', 'key' : 'person_id' }, - { 'title': 'Third parties'|trans, + { 'title': 'Third parties'|trans, 'items': entity.thirdParties, 'path' : 'chill_3party_3party_show', 'key' : 'thirdparty_id' }, - { 'title': 'Users concerned'|trans, + { 'title': 'Users concerned'|trans, 'items': entity.users, 'path' : 'admin_user_show', 'key' : 'id' @@ -23,22 +23,22 @@ ] %} {% else %} {% set blocs = [ - { 'title': 'Persons in accompanying course'|trans, + { 'title': 'Persons in accompanying course'|trans, 'items': entity.personsAssociated, 'path' : 'chill_person_view', 'key' : 'person_id' }, - { 'title': 'Third persons'|trans, + { 'title': 'Third persons'|trans, 'items': entity.personsNotAssociated, 'path' : 'chill_person_view', 'key' : 'person_id' }, - { 'title': 'Third parties'|trans, + { 'title': 'Third parties'|trans, 'items': entity.thirdParties, 'path' : 'chill_3party_3party_show', 'key' : 'thirdparty_id' }, - { 'title': 'Users concerned'|trans, + { 'title': 'Users concerned'|trans, 'items': entity.users, 'path' : 'admin_user_show', 'key' : 'id' @@ -101,3 +101,31 @@ {% endfor %} {% endif %} + +{% if (with_display == 'wrap-list') %} +
+ {% for bloc in blocs %} +
+ {% if bloc.items|length > 0 %} +
+

{{ bloc.title }}

+
+ + {% endif %} +
+ {% endfor %} +
+{% endif %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig index c227f5539..fef381527 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig @@ -1,4 +1,4 @@ -
+
{% if activities|length == 0 %}

@@ -7,176 +7,56 @@

{% else %} -
+
{% for activity in activities %} {% set t = activity.type %}
-
-
- - {% if activity.date %} -

{{ activity.date|format_date('long') }}

- {% endif %} - -
- {% if activity.durationTime and t.durationTimeVisible %} -

- - {{ activity.durationTime|date('H:i') }} -

- {% endif %} - {% if activity.travelTime and t.travelTimeVisible %} -

- - {{ activity.travelTime|date('H:i') }} -

- {% endif %} -
- - {% if context == 'person' and activity.accompanyingPeriod is not empty %} -
- - -
- {% endif %} - -
-
- -
-
- -
-
    -
  • - -
  • - {% if no_action is not defined or no_action == false %} - {# TODO - {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %} - #} -
  • - -
  • - {# TODO - {% endif %} - {% if is_granted('CHILL_ACTIVITY_DELETE', activity) %} - #} -
  • - -
  • - {# TODO - {% endif %} - #} - {% endif %} -
-
- -
    - - {% if activity.user and t.userVisible %} -
  • - ref: - {{ activity.user.usernameCanonical }} -
  • - {% endif %} - -
  • - {{ activity.type.name | localize_translatable_string }} - - {% if activity.attendee is not null and t.attendeeVisible %} - {% if activity.attendee %} - {{ '→ ' ~ 'present'|trans|capitalize }} - {% else %} - {{ '→ ' ~ 'not present'|trans|capitalize }} - {% endif %} - {% endif %} -
  • - -
  • - {{ 'location'|trans ~ ': ' }} - Domicile de l'usager - - {# TODO {% if activity.location %}{{ activity.location }}{% endif %} - #} - -
  • - - {%- if t.reasonsVisible -%} -
  • - {%- if activity.reasons is not empty -%} - {% for r in activity.reasons %} - {{ r|chill_entity_render_box }} - {% endfor %} - {%- endif -%} -
  • - {% endif %} - - {%- if t.socialIssuesVisible %} - - {% endif %} - - {%- if t.socialActionsVisible -%} - - {% endif %} - - -
- - - - -
-
- -
+
+ {% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
- {% if activity.comment.comment is not empty or activity.persons|length > 0 or activity.thirdParties|length > 0 or activity.users|length > 0 %} -
- {% if activity.comment.comment is not empty %} - {{ activity.comment|chill_entity_render_box({ - 'disable_markdown': false, - 'limit_lines': 3, - 'metadata': false, - }) }} - {% endif %} -
-
- {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { - 'context': context, - 'with_display': 'bloc', - 'entity': activity, - 'badge_person': true - } %} -
+
+ {% if activity.comment.comment is not empty %} + {{ activity.comment|chill_entity_render_box({ + 'disable_markdown': false, + 'limit_lines': 3, + 'metadata': false, + }) }} + {% endif %} + + {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { + 'context': context, + 'with_display': 'row', + 'entity': activity, + 'badge_person': true + } %} +
{% endif %} +
+
+
    +
  • + +
  • + {% if no_action is not defined or no_action == false %} +
  • + +
  • +
  • + +
  • + {% endif %} +
+
{% endfor %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list_recent.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list_recent.html.twig new file mode 100644 index 000000000..567c0b132 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list_recent.html.twig @@ -0,0 +1,10 @@ +
+ {% for activity in activities | slice(0,5) %} + {% set t = activity.type %} + + + + {% include '@ChillActivity/Activity/activity-badge-title.html.twig' %} + + {% endfor %} +
diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig index 1f54692d9..eba074e07 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig @@ -73,7 +73,7 @@ {% endif %} {% if t.travelTimeVisible %} -
{{ 'Travel Time'|trans }}
+
{{ 'Travel time'|trans }}
{% if entity.travelTime is not null %} {{ entity.travelTime|date('H:i') }} {% else %} diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 679004e2e..fd7dc64f8 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -210,3 +210,5 @@ Group by reasons: Sujet d'activité Aggregate by activity user: Aggréger par utilisateur lié à l'activité Aggregate by activity type: Aggréger par type d'activité Aggregate by activity reason: Aggréger par sujet de l'activité + +Last activities: Les dernières activités diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php deleted file mode 100644 index ae04b2110..000000000 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php +++ /dev/null @@ -1,20 +0,0 @@ - - * @author Champs Libres - */ -class AdminController extends AbstractController -{ - - public function redirectToAdminIndexAction() - { - return $this->redirectToRoute('chill_main_admin_central'); - } -} \ No newline at end of file diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityCategoryController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityCategoryController.php index 37f10c343..bf062b50d 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityCategoryController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityCategoryController.php @@ -3,6 +3,9 @@ namespace Chill\AsideActivityBundle\Controller; use Chill\MainBundle\CRUD\Controller\CRUDController; +use Chill\MainBundle\Pagination\PaginatorInterface; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\HttpFoundation\Request; /** @@ -10,5 +13,12 @@ use Chill\MainBundle\CRUD\Controller\CRUDController; */ class AsideActivityCategoryController extends CRUDController { + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) + { + /** @var QueryBuilder $query */ + $query->addOrderBy('e.ordering', 'ASC'); -} \ No newline at end of file + return $query; + } + +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php index 88e53c8d2..3e3b38033 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php @@ -4,7 +4,10 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Controller; +use Chill\AsideActivityBundle\Entity\AsideActivity; +use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository; use Chill\MainBundle\CRUD\Controller\CRUDController; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; use Doctrine\ORM\QueryBuilder; use Symfony\Component\HttpFoundation\Request; use Chill\MainBundle\Pagination\PaginatorInterface; @@ -12,12 +15,21 @@ use Doctrine\Common\Collections\Criteria; final class AsideActivityController extends CRUDController { - protected function buildQueryEntities(string $action, Request $request) + + private $categoryRepository; + + public function __construct(AsideActivityCategoryRepository $categoryRepository) { - $qb = parent::buildQueryEntities($action, $request); + $this->categoryRepository = $categoryRepository; + } + + protected function buildQueryEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null) + { + $qb = parent::buildQueryEntities($action, $request, $filterOrder); if ('index' === $action) { - $qb->andWhere($qb->expr()->eq('e.agent', ':user')); + $qb->where($qb->expr()->eq('e.agent', ':user')); + $qb->orWhere($qb->expr()->eq('e.createdBy', ':user')); $qb->setParameter('user', $this->getUser()); } @@ -37,5 +49,24 @@ final class AsideActivityController extends CRUDController return parent::orderQuery($action, $query, $request, $paginator); } + public function createEntity(string $action, Request $request): object + { + $asideActivity = new AsideActivity(); + $duration = $request->query->get('duration', '300'); + $duration = \DateTime::createFromFormat('U', $duration); + $asideActivity->setDuration($duration); + + $categoryId = $request->query->get('type', 7); + if($categoryId === null){ + return $this->createNotFoundException('You must give a valid category id'); + } + $category = $this->categoryRepository->find($categoryId); + $asideActivity->setType($category); + + $note = $request->query->get('note', null); + $asideActivity->setNote($note); + + return $asideActivity; + } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php index 712649653..59e77c3e9 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php @@ -24,6 +24,12 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte */ public function load(array $configs, ContainerBuilder $container): void { + + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + $container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services.yaml'); $loader->load('services/form.yaml'); @@ -81,7 +87,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte 'controller' => \Chill\AsideActivityBundle\Controller\AsideActivityController::class, 'actions' => [ 'index' => [ - 'template' => '@ChillAsideActivity/asideActivity/list.html.twig', + 'template' => '@ChillAsideActivity/asideActivity/index.html.twig', 'role' => 'ROLE_USER' ], 'new' => [ diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php new file mode 100644 index 000000000..7268b92ae --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php @@ -0,0 +1,138 @@ +getRootNode('chill_aside_activity') + ->children() + ->arrayNode('form') + ->canBeEnabled() + ->children() + ->arrayNode('time_duration') + ->isRequired() + ->defaultValue( + [ + [ 'label' => '5 minutes', 'seconds' => 300], + [ 'label' => '10 minutes', 'seconds' => 600], + [ 'label' => '15 minutes', 'seconds' => 900], + [ 'label' => '20 minutes', 'seconds' => 1200], + [ 'label' => '25 minutes', 'seconds' => 1500], + [ 'label' => '30 minutes', 'seconds' => 1800], + [ 'label' => '45 minutes', 'seconds' => 2700], + [ 'label' => '1 hour', 'seconds' => 3600], + [ 'label' => '1 hour 15', 'seconds' => 4500], + [ 'label' => '1 hour 30', 'seconds' => 5400], + [ 'label' => '1 hour 45', 'seconds' => 6300], + [ 'label' => '2 hours', 'seconds' => 7200], + [ 'label' => '2 hours 30', 'seconds' => 9000], + [ 'label' => '3 hours', 'seconds' => 10800], + [ 'label' => '3 hours 30', 'seconds' => 12600], + [ 'label' => '4 hours', 'seconds' => 14400], + [ 'label' => '4 hours 30', 'seconds' => 16200], + [ 'label' => '5 hours', 'seconds' => 18000], + [ 'label' => '5 hours 30', 'seconds' => 19800], + [ 'label' => '6 hours', 'seconds' => 21600], + [ 'label' => '6 hours 30', 'seconds' => 23400], + [ 'label' => '7 hours', 'seconds' => 25200], + [ 'label' => '7 hours 30', 'seconds' => 27000], + [ 'label' => '8 hours', 'seconds' => 28800], + [ 'label' => '8 hours 30', 'seconds' => 30600], + [ 'label' => '9 hours', 'seconds' => 32400], + [ 'label' => '9 hours 30', 'seconds' => 34200], + [ 'label' => '10 hours', 'seconds' => 36000], + [ 'label' => '1/2 day', 'seconds' => 14040], + [ 'label' => '1 day', 'seconds' => 28080], + [ 'label' => '1 1/2 days', 'seconds' => 42120], + [ 'label' => '2 days', 'seconds' => 56160], + [ 'label' => '2 1/2 days', 'seconds' => 70200], + [ 'label' => '3 days', 'seconds' => 84240], + [ 'label' => '3 1/2 days', 'seconds' => 98280], + [ 'label' => '4 days', 'seconds' => 112320], + [ 'label' => '4 1/2 days', 'seconds' => 126360], + [ 'label' => '5 days', 'seconds' => 140400], + [ 'label' => '5 1/2 days', 'seconds' => 154440], + [ 'label' => '6 days', 'seconds' => 168480], + [ 'label' => '6 1/2 days', 'seconds' => 182520], + [ 'label' => '7 days', 'seconds' => 196560], + [ 'label' => '7 1/2 days', 'seconds' => 210600], + [ 'label' => '8 days', 'seconds' => 224640], + [ 'label' => '8 1/2 days', 'seconds' => 238680], + [ 'label' => '9 days', 'seconds' => 252720], + [ 'label' => '9 1/2 days', 'seconds' => 266760], + [ 'label' => '10 days', 'seconds' => 280800], + [ 'label' => '10 1/2days', 'seconds' => 294840], + [ 'label' => '11 days', 'seconds' => 308880], + [ 'label' => '11 1/2 days', 'seconds' => 322920], + [ 'label' => '12 days', 'seconds' => 336960], + [ 'label' => '12 1/2 days', 'seconds' => 351000], + [ 'label' => '13 days', 'seconds' => 365040], + [ 'label' => '13 1/2 days', 'seconds' => 379080], + [ 'label' => '14 days', 'seconds' => 393120], + [ 'label' => '14 1/2 days', 'seconds' => 407160], + [ 'label' => '15 days', 'seconds' => 421200], + [ 'label' => '15 1/2 days', 'seconds' => 435240], + [ 'label' => '16 days', 'seconds' => 449280], + [ 'label' => '16 1/2 days', 'seconds' => 463320], + [ 'label' => '17 days', 'seconds' => 477360], + [ 'label' => '17 1/2 days', 'seconds' => 491400], + [ 'label' => '18 days', 'seconds' => 505440], + [ 'label' => '18 1/2 days', 'seconds' => 519480], + [ 'label' => '19 days', 'seconds' => 533520], + [ 'label' => '19 1/2 days', 'seconds' => 547560], + [ 'label' => '20 days', 'seconds' => 561600], + [ 'label' => '20 1/2 days', 'seconds' => 575640], + [ 'label' => '21 days', 'seconds' => 580680], + [ 'label' => '21 1/2 days', 'seconds' => 603720], + [ 'label' => '22 days', 'seconds' => 617760], + [ 'label' => '22 1/2 days', 'seconds' => 631800], + [ 'label' => '23 days', 'seconds' => 645840], + [ 'label' => '23 1/2 days', 'seconds' => 659880], + [ 'label' => '24 days', 'seconds' => 673920], + [ 'label' => '24 1/2 days', 'seconds' => 687960], + [ 'label' => '25 days', 'seconds' => 702000], + [ 'label' => '25 1/2 days', 'seconds' => 716040], + [ 'label' => '26 days', 'seconds' => 730080], + [ 'label' => '26 1/2 days', 'seconds' => 744120], + [ 'label' => '27 days', 'seconds' => 758160], + [ 'label' => '27 1/2 days', 'seconds' => 772200], + [ 'label' => '28 days', 'seconds' => 786240], + [ 'label' => '28 1/2 days', 'seconds' => 800280], + [ 'label' => '29 days', 'seconds' => 814320], + [ 'label' => '29 1/2 days', 'seconds' => 828360], + [ 'label' => '30 days', 'seconds' => 842400], + ] + ) + ->info('The intervals of time to show in activity form') + + ->prototype('array') + ->children() + ->scalarNode('seconds') + ->info("The number of seconds of this duration. Must be an integer.") + ->cannotBeEmpty() + ->validate() + ->ifTrue(function($data) { + return !is_int($data); + })->thenInvalid("The value %s is not a valid integer") + ->end() + ->end() + ->scalarNode('label') + ->cannotBeEmpty() + ->info("The label to show into fields") + ->end() + ->end() + ->end() + ->end() + ->end() + ->end(); + + return $treeBuilder; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php index 6c580394b..09087226c 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php @@ -7,6 +7,8 @@ namespace Chill\AsideActivityBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @ORM\Entity @@ -27,10 +29,33 @@ class AsideActivityCategory private array $title; /** + * @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent") * @ORM\Column(type="boolean") */ private bool $isActive = true; + /** + * @var float + * @ORM\Column(type="float", options={"default": 0.00}) + */ + private float $ordering = 0.00; + + /** + * @ORM\ManyToOne(targetEntity=AsideActivityCategory::class, inversedBy="children") + * @ORM\JoinColumn(nullable=true) + */ + private $parent; + + /** + * @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent") + */ + private $children; + + public function __construct() + { + $this->children = new ArrayCollection(); + } + public function getId(): ?int { return $this->id; @@ -59,4 +84,85 @@ class AsideActivityCategory return $this; } + + public function getParent(): ?self + { + return $this->parent; + } + + /** + * + * @Assert\Callback() + */ + public function preventRecursiveParent(ExecutionContextInterface $context, $payload) + { + if (!$this->hasParent()) { + return; + } + + if ($this->getParent() === $this) { + // replace parent with old parent. This prevent recursive loop + // when displaying form + $this->parent = $this->oldParent; + $context->buildViolation('You must not add twice the same category in the parent tree (previous result returned)') + ->atPath('parent') + ->addViolation() + ; + } + } + + public function hasParent(): bool + { + return $this->parent !== null; + } + + public function setParent(?self $parent): self + { + // cache the old result for changing it during validaiton + $this->oldParent = $this->parent; + $this->parent = $parent; + + return $this; + } + + /** + * @return Collection|self[] + */ + public function getChildren(): Collection + { + return $this->children; + } + + public function addChild(self $child): self + { + if (!$this->children->contains($child)) { + $this->children[] = $child; + $child->setParent($this); + } + + return $this; + } + + public function removeChild(self $child): self + { + if ($this->children->removeElement($child)) { + // set the owning side to null (unless already changed) + if ($child->getParent() === $this) { + $child->setParent(null); + } + } + + return $this; + } + + public function getOrdering(): float + { + return $this->ordering; + } + + public function setOrdering(float $ordering): AsideActivityCategory + { + $this->ordering = $ordering; + return $this; + } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php index 1bfc6ed37..0060ab1b9 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php @@ -3,21 +3,43 @@ namespace Chill\AsideActivityBundle\Form; use Chill\AsideActivityBundle\Entity\AsideActivityCategory; +use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; use Chill\MainBundle\Form\Type\TranslatableStringFormType; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; - +use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\FormBuilderInterface; final class AsideActivityCategoryType extends AbstractType { + + protected $translatableStringHelper; + + public function __construct(TranslatableStringHelper $translatableStringHelper, CategoryRender $categoryRender) + { + $this->translatableStringHelper = $translatableStringHelper; + $this->categoryRender = $categoryRender; + } + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('title', TranslatableStringFormType::class, [ 'label' => 'Nom', ]) - ->add('isActive', ChoiceType::class, + ->add('parent', EntityType::class, [ + 'class' => AsideActivityCategory::class, + 'required' => false, + 'label' => 'Parent', + 'choice_label' => function (AsideActivityCategory $category){ + $options = []; + return $this->categoryRender->renderString($category, $options); + } + ]) + ->add('ordering', NumberType::class) + ->add('isActive', ChoiceType::class, [ 'choices' => [ 'Yes' => true, diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php index 4bf5bbfdc..7c7803816 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php @@ -4,11 +4,13 @@ namespace Chill\AsideActivityBundle\Form; use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Entity\AsideActivityCategory; +use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\AbstractType; @@ -16,26 +18,27 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTra use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Templating\EngineInterface; final class AsideActivityFormType extends AbstractType { protected array $timeChoices; - private TranslatableStringHelper $translatableStringHelper; private TokenStorageInterface $storage; + private CategoryRender $categoryRender; public function __construct ( TranslatableStringHelper $translatableStringHelper, ParameterBagInterface $parameterBag, - TokenStorageInterface $storage + TokenStorageInterface $storage, + CategoryRender $categoryRender ){ - $this->timeChoices = $parameterBag->get('chill_activity.form.time_duration'); + $this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration'); $this->translatableStringHelper = $translatableStringHelper; $this->storage = $storage; + $this->categoryRender = $categoryRender; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -70,11 +73,6 @@ final class AsideActivityFormType extends AbstractType [ 'label' => 'date', 'data' => new \DateTime(), - //SETTING RANGE ONLY POSSIBLE WITH WIDGET 'CHOICE' AND NOT 'SINGLE_TEXT'? - // 'widget' => 'choice', - // 'years' => range(2020, date('Y')), - // 'months' => range(1, date('m')), - // 'days' => range(1, date('d')), 'required' => true ]) ->add('type', EntityType::class, @@ -83,9 +81,17 @@ final class AsideActivityFormType extends AbstractType 'required' => true, 'class' => AsideActivityCategory::class, 'placeholder' => 'Choose the activity category', - // 'choice_label' => 'title[""]' + 'query_builder' => function(EntityRepository $er) { + $qb = $er->createQueryBuilder('ac'); + $qb->where($qb->expr()->eq('ac.isActive', 'TRUE')) + ->addOrderBy('ac.ordering', 'ASC') + ; + + return $qb; + }, 'choice_label' => function (AsideActivityCategory $asideActivityCategory) { - return $this->translatableStringHelper->localize($asideActivityCategory->getTitle()); + $options = []; + return $this->categoryRender->renderString($asideActivityCategory, $options); }, ]) ->add('duration', ChoiceType::class, $durationTimeOptions) diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php new file mode 100644 index 000000000..ecc1a1f90 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php @@ -0,0 +1,48 @@ +security = $security; + } + + public static function getMenuIds(): array + { + return ['admin_index', 'admin_section', 'admin_aside_activity']; + } + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + // all the entries below must have ROLE_ADMIN permissions + if (!$this->security->isGranted('ROLE_ADMIN')) { + return; + } + + if (in_array($menuId, ['admin_index', 'admin_section'])) { + $menu->addChild('Aside activities', [ + 'route' => 'chill_crud_aside_activity_category_index' + ]) + ->setExtras([ + 'order' => 900, + 'explain' => "Configure aside activities categories" + ]); + } else { + $menu + ->addChild('Aside activity categories', [ + 'route' => 'chill_crud_aside_activity_category_index' + ]) + ->setExtras([ + 'order' => '50' + ]); + } + + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php index b9776b92b..8a8d65156 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php @@ -4,6 +4,8 @@ namespace Chill\AsideActivityBundle\Menu; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Knp\Menu\MenuItem; +use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -14,10 +16,12 @@ use Symfony\Contracts\Translation\TranslatorInterface; class SectionMenuBuilder implements LocalMenuBuilderInterface { protected TranslatorInterface $translator; + public AuthorizationCheckerInterface $authorizationChecker; - public function __construct(TranslatorInterface $translator) + public function __construct(TranslatorInterface $translator, AuthorizationCheckerInterface $authorizationChecker) { $this->translator = $translator; + $this->authorizationChecker = $authorizationChecker; } /** @@ -27,13 +31,26 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface */ public function buildMenu($menuId, MenuItem $menu, array $parameters) { - $menu->addChild($this->translator->trans('Create an aside activity'), [ - 'route' => 'chill_crud_aside_activity_new' + if ($this->authorizationChecker->isGranted('ROLE_USER')){ + $menu->addChild($this->translator->trans('Create an aside activity'), [ + 'route' => 'chill_crud_aside_activity_new' + ]) + ->setExtras([ + 'order' => 11, + 'icons' => [ 'plus' ] + ]); + $menu->addChild($this->translator->trans('Phonecall'), [ + 'route' => 'chill_crud_aside_activity_new', + 'routeParameters' => [ + 'type' => 1, + 'duration' => 900, + ] ]) ->setExtras([ - 'order' => 11, - 'icons' => [ 'plus' ] + 'order' => 12, + 'icons' => ['plus'] ]); + } } /** diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php new file mode 100644 index 000000000..4858e6ee7 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php @@ -0,0 +1,92 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\AsideActivityBundle\Menu; + +use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Knp\Menu\MenuItem; +use Chill\TaskBundle\Templating\UI\CountNotificationTask; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Chill\MainBundle\Entity\User; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; + + +/** + * + * + * @author Julien Fastré + */ +class UserMenuBuilder implements LocalMenuBuilderInterface +{ + + /** + * + * @var CountNotificationTask + */ + public $counter; + + /* + * @var TokenStorageInterface + */ + public $tokenStorage; + + /** + * + * @var TranslatorInterface + */ + public $translator; + + /** + * + * @var AuthorizationCheckerInterface + */ + public $authorizationChecker; + + public function __construct( + CountNotificationTask $counter, + TokenStorageInterface $tokenStorage, + TranslatorInterface $translator, + AuthorizationCheckerInterface $authorizationChecker + ) { + $this->counter = $counter; + $this->tokenStorage = $tokenStorage; + $this->translator = $translator; + $this->authorizationChecker = $authorizationChecker; + } + + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + if ($this->authorizationChecker->isGranted('ROLE_USER')){ + $menu->addChild("My aside activities", [ + 'route' => 'chill_crud_aside_activity_index' + ]) + ->setExtras([ + 'order' => 10, + 'icon' => 'tasks' + ]); + } + + } + + public static function getMenuIds(): array + { + return [ 'user' ]; + } + +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/layout_asideactivity.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/layout_asideactivity.html.twig index f0242c085..3f44e432d 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/layout_asideactivity.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/layout_asideactivity.html.twig @@ -1,14 +1,12 @@ {% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %} {% block vertical_menu_content %} - {{ chill_menu('admin_aside_activity', { - 'layout': '@ChillAsideActivity/Admin/menu_asideactivity.html.twig', - }) }} + {{ chill_menu('admin_aside_activity') }} {% endblock %} {% block layout_wvm_content %} {% block admin_content %} -

{{ 'Aside activity configuration' |trans }}

+

{{ 'Aside activity configuration'|trans }}

{% endblock %} {% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/menu_asideactivity.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/menu_asideactivity.html.twig deleted file mode 100644 index 34ea309e5..000000000 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/menu_asideactivity.html.twig +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "@ChillMain/Menu/verticalMenu.html.twig" %} -{% block v_menu_title %} - {{ 'Aside activity configuration menu'|trans }} -{% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Entity/asideActivityCategory.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Entity/asideActivityCategory.html.twig new file mode 100644 index 000000000..1b31cc765 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Entity/asideActivityCategory.html.twig @@ -0,0 +1,12 @@ +{% set reversed_parents = parents|reverse %} + + {%- for p in reversed_parents %} + + {{ p.title|localize_translatable_string }}{{ options['default.separator'] }} + + {%- endfor -%} + + {{ asideActivityCategory.title|localize_translatable_string }} + + diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_index.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_index.html.twig deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig index 0e8ea0506..6cc45aeaf 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig @@ -1,8 +1,91 @@ -{% extends '@ChillMain/layout.html.twig' %} +{% extends "@ChillMain/layout.html.twig" %} -{% block title %}{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}{% endblock %} +{% block title %} + {{ 'Aside activity list'|trans }} +{% endblock title %} {% block content %} - {% embed '@ChillAsideActivity/AsideActivity/_index.html.twig' %} - {% endembed %} -{% endblock content %} \ No newline at end of file +
+

{{ 'My aside activities'|trans }}

+ + {% if entities|length == 0 %} +

+ {{ "There aren't any aside activities."|trans }} + +

+ {% else %} + +
+ + {% for entity in entities %} + +
+
+ +
+

+ {{ entity.type|chill_entity_render_box }} +

+
+ {% if entity.date %} + {{ entity.date|format_date('long') }} + {% endif %} + {% if entity.date %} + + + + {{ entity.duration|date('H:i') }} + + + {% endif %} +
+
+
+
+
    +
  • + + {{ 'By'|trans }}: + {{ entity.createdBy|chill_entity_render_box }} + +
  • +
  • + + {{ 'For'|trans }}: + {{ entity.agent|chill_entity_render_box }} + + +
  • +
+
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+
+
+ {% endfor %} +
+ + {{ chill_pagination(paginator) }} + + + {% endif %} + + {% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/list.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/list.html.twig deleted file mode 100644 index d0e02e3d2..000000000 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/list.html.twig +++ /dev/null @@ -1,97 +0,0 @@ -{% extends "@ChillMain/layout.html.twig" %} - -{% block title %} - {{ 'Aside activity list' |trans }} -{% endblock title %} - -{% block content %} -
-

{{ 'My aside activities' |trans }}

- - {% if entities|length == 0 %} -

- {{ "There aren't any aside activities."|trans }} - -

- {% else %} - -
- {# Sort activities according to date in descending order #} - {% for entity in entities %} - {% set t = entity.type %} - -
-
-
- -

- {{ entity.type.title | localize_translatable_string }} -

- - {% if entity.date %} -

{{ entity.date|format_date('long') }}

- {% endif %} - -
-

- - {{ entity.duration|date('H:i') }} -

-
- -
-
-
    - {% if entity.createdBy %} -
  • - {{ 'Created by: '|trans }}{{ entity.createdBy.usernameCanonical }} -
  • - {% endif %} -
-
-
- - {# {% - if entity.note is not empty - or entity.createdBy|length > 0 - %} -
- {% if entity.note is not empty %} -
- {{ entity.note|chill_markdown_to_html }} -
- {% endif %} - -
- {% endif %} #} -
-
    -
      -
    • - -
    • -
    • - -
    • -
    -
-
-
- - {% endfor %} -
- - {{ chill_pagination(paginator) }} - - -
- {% endif %} - -{% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig index 4553e1c6f..75da9a444 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig @@ -3,13 +3,50 @@ {% set activeRouteKey = '' %} {% block title %} -{% include('@ChillMain/CRUD/_view_title.html.twig') %} + {% include('@ChillMain/CRUD/_view_title.html.twig') %} {% endblock %} {% block content %} -{% embed '@ChillMain/CRUD/_view_content.html.twig' %} - {% block crud_content_header %} -

{{ ('crud.' ~ crud_name ~ '.title_view')|trans }}

- {% endblock crud_content_header %} -{% endembed %} -{% endblock %} \ No newline at end of file + {% embed '@ChillMain/CRUD/_view_content.html.twig' %} + {% block crud_content_header %} +

{{ ('crud.' ~ crud_name ~ '.title_view')|trans }}

+ {% endblock crud_content_header %} + {% block crud_content_view_details %} +
+ +
{{ 'Type'|trans }}
+
{{ entity.type|chill_entity_render_box }}
+ +
{{ 'Created by'|trans }}
+
{{ entity.createdBy }}
+ +
{{ 'Created for'|trans }}
+
{{ entity.agent }}
+ +

{{ 'Activity data'|trans }}

+ +
{{ 'Date'|trans }}
+
{{ entity.date|format_date('long') }}
+ +
{{ 'Duration'|trans }}
+
{{ entity.duration|date('H:i') }}
+ +
{{ 'Remark'|trans }}
+ {%- if entity.note is empty -%} +
+ {{ 'No comments'|trans }} +
+ {%- else -%} +
+
+
+

{{ entity.note }}

+
+
+
+ {%- endif -%} +
+ + {% endblock %} + {% endembed %} +{% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/edit.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/edit.html.twig index dacee767c..3fb6fb612 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/edit.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/edit.html.twig @@ -1,4 +1,4 @@ -{% extends "@ChillActivity/Admin/layout_activity.html.twig" %} +{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %} {% block title %} {% include('@ChillMain/CRUD/_edit_title.html.twig') %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/index.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/index.html.twig index d8cb67716..2c8373daf 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/index.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/index.html.twig @@ -1,11 +1,12 @@ {% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %} {% block admin_content %} -

{{ 'ActivityType list'|trans }}

+

{{ 'Aside Activity Type list'|trans }}

+ @@ -14,7 +15,8 @@ {% for entity in entities %} - + +
{{ 'Ordering'|trans }} {{ 'Name'|trans }} {{ 'Active'|trans }} {{ 'Actions'|trans }}
{{ entity.title|localize_translatable_string }}{{ entity.ordering }}{{ entity|chill_entity_render_box }} {%- if entity.isActive -%} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/new.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/new.html.twig index d7ac89752..3c8ded234 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/new.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/new.html.twig @@ -1,4 +1,4 @@ -{% extends "@ChillActivity/Admin/layout_activity.html.twig" %} +{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %} {% block title %} {% include('@ChillMain/CRUD/_new_title.html.twig') %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php b/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php new file mode 100644 index 000000000..902ed7a68 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php @@ -0,0 +1,80 @@ + ' > ' + ]; + + public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine) + { + $this->translatableStringHelper = $translatableStringHelper; + $this->engine = $engine; + } + + /** + * @param AsideActivityCategory $asideActivityCategory + */ + public function renderString($asideActivityCategory, array $options): string + { + $options = array_merge(self::DEFAULT_ARGS, $options); + + $titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle()); + + while ($asideActivityCategory->hasParent()) { + $asideActivityCategory = $asideActivityCategory->getParent(); + $titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle()); + } + + $titles = array_reverse($titles); + + return implode($options[self::SEPERATOR_KEY], $titles); + + } + + /** + * @param AsideActivityCategory $asideActivityCategory + */ + public function supports($asideActivityCategory, array $options): bool + { + return $asideActivityCategory instanceof AsideActivityCategory; + } + + public function buildParents(AsideActivityCategory $asideActivityCategory) + { + $parents = []; + + while($asideActivityCategory->hasParent()) { + $asideActivityCategory = $parents[] = $asideActivityCategory->getParent(); + } + + return $parents; + } + + /** + * @param AsideActivityCategory $asideActivityCategory + */ + public function renderBox($asideActivityCategory, array $options): string + { + $options = array_merge(self::DEFAULT_ARGS, $options); + $parents = $this->buildParents($asideActivityCategory); + + return $this->engine->render('@ChillAsideActivity/Entity/asideActivityCategory.html.twig', + [ + 'asideActivityCategory' => $asideActivityCategory, + 'parents' => $parents, + 'options' => $options + ]); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml index 8ff656fdc..34bb6da33 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml @@ -1,5 +1,22 @@ services: - Chill\AsideActivityBundle\DataFixtures\: - resource: './../DataFixtures' - autowire: true - autoconfigure: true + Chill\AsideActivityBundle\DataFixtures\: + resource: "./../DataFixtures" + autowire: true + autoconfigure: true + + Chill\AsideActivityBundle\Templating\Entity\: + autowire: true + autoconfigure: true + resource: "../Templating/Entity" + tags: + - "chill.render_entity" + + Chill\AsideActivityBundle\Repository\: + resource: "../Repository" + autowire: true + autoconfigure: true + + Chill\AsideActivityBundle\Controller\: + resource: "../Controller" + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services/menu.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services/menu.yaml index 614391a0e..909f82381 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/services/menu.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services/menu.yaml @@ -1,5 +1,5 @@ services: Chill\AsideActivityBundle\Menu\: - resource: './../../Menu' + resource: "./../../Menu" autowire: true autoconfigure: true diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210706124644.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210706124644.php index 59fa35be4..13bee6404 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210706124644.php +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210706124644.php @@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; -/** - * Auto-generated Migration: Please modify to your needs! - */ final class Version20210706124644 extends AbstractMigration { public function getDescription(): string { - return ''; + return 'Aside activity category entity created'; } public function up(Schema $schema): void diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210804082249.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210804082249.php index 4135bb4a2..85b99c572 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210804082249.php +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210804082249.php @@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; -/** - * Auto-generated Migration: Please modify to your needs! - */ final class Version20210804082249 extends AbstractMigration { public function getDescription(): string { - return ''; + return 'Aside activity entity created'; } public function up(Schema $schema): void @@ -59,12 +56,12 @@ final class Version20210804082249 extends AbstractMigration $this->addSql('CREATE INDEX idx_e9fa219165ff1aec ON asideactivity (updatedby_id)'); $this->addSql('CREATE INDEX idx_e9fa21913414710b ON asideactivity (agent_id)'); $this->addSql('COMMENT ON COLUMN asideactivity.updatedat IS \'(DC2Type:datetime_immutable)\''); - $this->addSql('CREATE TABLE chill_main_address_legacy (id INT DEFAULT NULL, postcode_id INT DEFAULT NULL, street VARCHAR(255) DEFAULT NULL, streetnumber VARCHAR(255) DEFAULT NULL, validfrom DATE DEFAULT NULL, isnoaddress BOOLEAN DEFAULT NULL, customs JSONB DEFAULT NULL, floor VARCHAR(16) DEFAULT NULL, corridor VARCHAR(16) DEFAULT NULL, steps VARCHAR(16) DEFAULT NULL, buildingname VARCHAR(255) DEFAULT NULL, flat VARCHAR(16) DEFAULT NULL, distribution VARCHAR(255) DEFAULT NULL, extra VARCHAR(255) DEFAULT NULL, validto DATE DEFAULT NULL, point VARCHAR(255) DEFAULT NULL, linkedtothirdparty_id INT DEFAULT NULL)'); $this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa2191c54c8c93 FOREIGN KEY (type_id) REFERENCES asideactivitytype (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa21913174800f FOREIGN KEY (createdby_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa219165ff1aec FOREIGN KEY (updatedby_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa21913414710b FOREIGN KEY (agent_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('DROP TABLE chill_asideactivity.AsideActivity'); $this->addSql('DROP TABLE chill_asideactivity.AsideActivityCategory'); + $this->addSql('DROP SCHEMA chill_asideactivity'); } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210806140343.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210806140343.php index 62b568407..75c7b81b2 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210806140343.php +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210806140343.php @@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; -/** - * Auto-generated Migration: Please modify to your needs! - */ final class Version20210806140343 extends AbstractMigration { public function getDescription(): string { - return ''; + return 'Duration changed to type integer'; } public function up(Schema $schema): void diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210806140710.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210806140710.php index 92320e639..5c572e650 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210806140710.php +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210806140710.php @@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; -/** - * Auto-generated Migration: Please modify to your needs! - */ final class Version20210806140710 extends AbstractMigration { public function getDescription(): string { - return ''; + return 'Duration changed back to timestamp'; } public function up(Schema $schema): void diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210810084456.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210810084456.php index 29d6be7f3..2984108c2 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210810084456.php +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210810084456.php @@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; -/** - * Auto-generated Migration: Please modify to your needs! - */ final class Version20210810084456 extends AbstractMigration { public function getDescription(): string { - return ''; + return 'createdat, updatedat and date given a type timestamp'; } public function up(Schema $schema): void diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210922182907.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210922182907.php new file mode 100644 index 000000000..13ba8749c --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20210922182907.php @@ -0,0 +1,28 @@ +addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD parent_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD CONSTRAINT FK_7BF90DBE727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_asideactivity.AsideActivityCategory (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_7BF90DBE727ACA70 ON chill_asideactivity.asideactivitycategory (parent_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_asideactivity.AsideActivityCategory DROP parent'); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20211004134012.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20211004134012.php new file mode 100644 index 000000000..42763638e --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20211004134012.php @@ -0,0 +1,29 @@ +addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD ordering DOUBLE PRECISION NOT NULL DEFAULT 0.00'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory DROP ordering'); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml index fa62befea..c16f6af6a 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml +++ b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml @@ -14,11 +14,8 @@ present: présent not present: absent Delete: Supprimer Update: Mettre à jour -Update activity: Modifier l'activité Aside activity data: Données de l'activité annexe -No reason associated: Aucun sujet There aren't any aside activities.: Aucune activité annexe enregistrée. -type_name: type de l'activité Type: Type Invisible: Invisible Optional: Optionnel @@ -43,14 +40,8 @@ crud: title_edit: Edition d'une catégorie de type d'activité #forms -Activity creation: Nouvelle activité annexe -Create a new aside activity type: Nouvelle catégorie d'activité annexe -Create: Créer +Create a new aside activity type: Nouvelle categorie d'activité annexe Back to the list: Retour à la liste -Save activity: Sauver l'activité -Reset form: Remise à zéro du formulaire -Choose the agent for whom this activity is created: Choisissez l'utilisateur pour qui l'activité est créée. -Choose the activity category: Choisissez le type d'activité Choose the duration: Choisir la durée Choose a category: Choisir une catégorie Is active: Actif @@ -58,12 +49,106 @@ Agent: Utilisateur date: Date Duration: Durée Note: Note +Choose the agent for whom this activity is created: Choisissez l'agent pour qui l'activité est créée +Choose the activity category: Choisir la catégorie + +#Duration +minutes: minutes +hour: heure +hours: heures +day: jour +days: jours +5 minutes: 5 minutes +10 minutes: 10 minutes +15 minutes: 15 minutes +20 minutes: 20 minutes +25 minutes: 25 minutes +30 minutes: 30 minutes +45 minutes: 45 minutes +1 hour: 1 heure +1 hour 15: 1 heure 15 +1 hour 30: 1 heure 30 +1 hour 45: 1 heure 45 +2 hours: 2 heures +2 hours 30: 2 heure 30 +3 hours: 3 heures +3 hours 30: 3 heure 30 +4 hours: 4 heures +4 hours 30: 4 heure 30 +5 hours: 5 heures +5 hours 30: 5 heure 30 +6 hours: 6 heures +6 hours 30: 6 heure 30 +7 hours: 7 heures +7 hours 30: 7 heure 30 +8 hours: 8 heures +8 hours 30: 8 heure 30 +9 hours: 9 heures +9 hours 30: 9 heure 30 +10 hours: 10 heures +1/2 day: 1/2 jour +1 day: 1 jour +1 1/2 days: 1 1/2 jours +2 days: 2 jours +2 1/2 days: 2 1/2 jours +3 days: 3 jours +3 1/2 days: 3 1/2 jours +4 days: 4 jours +4 1/2 days: 4 1/2 jours +5 days: 5 jours +5 1/2 days: 5 1/2 jours +6 days: 6 jours +6 1/2 days: 6 1/2 jours +7 days: 7 jours +7 1/2 days: 7 1/2 jours +8 days: 8 jours +8 1/2 days: 8 1/2 jours +9 days: 9 jours +9 1/2 days: 9 1/2 jours +10 days: 10 jours +10 1/2 days: 10 1/2 jours +11 days: 11 jours +11 1/2 days: 11 1/2 jours +12 days: 12 jours +12 1/2 days: 12 1/2 jours +13 days: 13 jours +13 1/2 days: 13 1/2 jours +14 days: 14 jours +14 1/2 days: 14 1/2 jours +15 days: 15 jours +15 1/2 days: 15 1/2 jours +16 days: 16 jours +16 1/2 days: 16 1/2 jours +17 days: 17 jours +17 1/2 days: 17 1/2 jours +18 days: 18 jours +18 1/2 days: 18 1/2 jours +19 days: 19 jours +19 1/2 days: 19 1/2 jours +20 days: 20 jours +20 1/2 days: 20 1/2 jours +21 days: 21 jours +21 1/2 days: 21 1/2 jours +22 days: 22 jours +22 1/2 days: 22 1/2 jours +23 days: 23 jours +23 1/2 days: 23 1/2 jours +24 days: 24 jours +24 1/2 days: 24 1/2 jours +25 days: 25 jours +25 1/2 days: 25 1/2 jours +26 days: 26 jours +26 1/2 days: 26 1/2 jours +27 days: 27 jours +27 1/2 days: 27 1/2 jours +28 days: 28 jours +28 1/2 days: 28 1/2 jours +29 days: 29 jours +29 1/2 days: 29 1/2 jours +30 days: 30 jours #list My aside activities: Mes activités annexes -Date: Date -Created by: Créée par - #Aside activity delete Delete aside activity: Supprimer une activité annexe Are you sure you want to remove the aside activity concerning "%name%" ?: Êtes-vous sûr de vouloir supprimer une activité annexe qui concerne "%name%" ? @@ -73,3 +158,4 @@ The activity has been successfully removed.: L'activité a été supprimée. Create an aside activity: "Créer une activité annexe" Aside activity configuration menu: "Menu de configuration des activités annexes" Aside activity configuration: "Configuration des activités annexes" +Phonecall: "Appel téléphonique" diff --git a/src/Bundle/ChillAsideActivityBundle/src/translations/validators.fr.yaml b/src/Bundle/ChillAsideActivityBundle/src/translations/validators.fr.yaml new file mode 100644 index 000000000..28883568c --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/translations/validators.fr.yaml @@ -0,0 +1 @@ +You must not add twice the same category in the parent tree (previous result returned): Il est interdit d'indiquer la même entité dans l'arbre des parents. Le résultat précédent a été rétabli diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php index d135cda39..9c657ae46 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php @@ -20,6 +20,8 @@ namespace Chill\MainBundle\CRUD\Controller; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -209,22 +211,24 @@ class CRUDController extends AbstractController * This method: * * 1. Launch `onPreIndex` - * x. check acl. If it does return a response instance, return it - * x. launch `onPostCheckACL`. If it does return a response instance, return it - * 1. count the items, using `countEntities` - * 2. build a paginator element from the the number of entities ; - * 3. Launch `onPreIndexQuery`. If it does return a response instance, return it - * 3. build a query, using `queryEntities` - * x. fetch the results, using `getQueryResult` - * x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it - * 4. create default parameters: + * 2. check acl. If it does return a response instance, return it + * 3. launch `onPostCheckACL`. If it does return a response instance, return it + * 4. count the items, using `countEntities` + * 5. build a paginator element from the the number of entities ; + * 6. Launch `onPreIndexQuery`. If it does return a response instance, return it + * 7. fetch the results, using `getQueryResult` + * + * Internally, this build a query, using `queryEntities` + * + * 8. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it + * 9. create default parameters: * * The default parameters are: * * * entities: the list en entities ; * * crud_name: the name of the crud ; * * paginator: a paginator element ; - * 5. Launch rendering, the parameter is fetch using `getTemplateFor` + * 10. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. * * @param string $action @@ -249,7 +253,8 @@ class CRUDController extends AbstractController return $response; } - $totalItems = $this->countEntities($action, $request); + $filterOrder = $this->buildFilterOrderHelper($action, $request); + $totalItems = $this->countEntities($action, $request, $filterOrder); $paginator = $this->getPaginatorFactory()->create($totalItems); $response = $this->onPreIndexBuildQuery($action, $request, $totalItems, @@ -259,16 +264,7 @@ class CRUDController extends AbstractController return $response; } - $query = $this->queryEntities($action, $request, $paginator); - - $response = $this->onPostIndexBuildQuery($action, $request, $totalItems, - $paginator, $query); - - if ($response instanceof Response) { - return $response; - } - - $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $query); + $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder); $response = $this->onPostIndexFetchQuery($action, $request, $totalItems, $paginator, $entities); @@ -280,7 +276,8 @@ class CRUDController extends AbstractController $defaultTemplateParameters = [ 'entities' => $entities, 'crud_name' => $this->getCrudName(), - 'paginator' => $paginator + 'paginator' => $paginator, + 'filter_order' => $filterOrder ]; return $this->render( @@ -289,6 +286,11 @@ class CRUDController extends AbstractController ); } + protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper + { + return null; + } + /** * @param string $action * @param Request $request @@ -361,9 +363,9 @@ class CRUDController extends AbstractController * @param PaginatorInterface $paginator * @return type */ - protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator) + protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null) { - $query = $this->buildQueryEntities($action, $request) + $query = $this->buildQueryEntities($action, $request, $filterOrder) ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) ->setMaxResults($paginator->getItemsPerPage()); @@ -393,11 +395,13 @@ class CRUDController extends AbstractController * @param Request $request * @param int $totalItems * @param PaginatorInterface $paginator - * @param mixed $query * @return mixed */ - protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) + protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, + ?FilterOrderHelper $filterOrder = null) { + $query = $this->queryEntities($action, $request, $paginator, $filterOrder); + return $query->getQuery()->getResult(); } @@ -408,9 +412,9 @@ class CRUDController extends AbstractController * @param Request $request * @return int */ - protected function countEntities(string $action, Request $request): int + protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int { - return $this->buildQueryEntities($action, $request) + return $this->buildQueryEntities($action, $request, $filterOrder) ->select('COUNT(e)') ->getQuery() ->getSingleScalarResult() @@ -1183,6 +1187,11 @@ class CRUDController extends AbstractController return $this->get(Resolver::class); } + protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface + { + return $this->get(FilterOrderHelperFactoryInterface::class); + } + /** * @return array */ @@ -1197,6 +1206,7 @@ class CRUDController extends AbstractController EventDispatcherInterface::class => EventDispatcherInterface::class, Resolver::class => Resolver::class, SerializerInterface::class => SerializerInterface::class, + FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class, ] ); } diff --git a/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php b/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php index ba51db779..21090a61f 100644 --- a/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php +++ b/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php @@ -19,6 +19,8 @@ namespace Chill\MainBundle\Command; +use Chill\MainBundle\Doctrine\Model\Point; +use Chill\MainBundle\Entity\Country; use Doctrine\ORM\EntityManager; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -69,6 +71,9 @@ class LoadPostalCodesCommand extends Command . "using the postal code and name. \n" . "The CSV file must have the following columns: " . "postal code, label, country code." + . "Optionally, the csv file can have the following " + . "columns after the country code: reference code, latitude, longitude, source. " + . "The latitude and longitude columns are supposed to be in WGS84 and expressed in decimal degrees. " . "The CSV file should not have any header row.") ->addArgument('csv_file', InputArgument::REQUIRED, "the path to " . "the csv file. See the help for specifications.") @@ -163,7 +168,7 @@ class LoadPostalCodesCommand extends Command } $em = $this->entityManager; $country = $em - ->getRepository('ChillMainBundle:Country') + ->getRepository(Country::class) ->findOneBy(array('countryCode' => $row[2])); if ($country === NULL) { @@ -173,7 +178,7 @@ class LoadPostalCodesCommand extends Command // try to find an existing postal code $existingPC = $em - ->getRepository('ChillMainBundle:PostalCode') + ->getRepository(PostalCode::class) ->findBy(array('code' => $row[0], 'name' => $row[1])); if (count($existingPC) > 0) { @@ -187,6 +192,18 @@ class LoadPostalCodesCommand extends Command ->setCountry($country) ; + if (NULL != $row[3]){ + $postalCode->setRefPostalCodeId($row[3]); + } + + if (NULL != $row[4] & NULL != $row[5]){ + $postalCode->setCenter(Point::fromLonLat((float) $row[5], (float) $row[4])); + } + + if (NULL != $row[6]){ + $postalCode->setPostalCodeSource($row[6]); + } + $errors = $this->validator->validate($postalCode); if ($errors->count() == 0) { diff --git a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdPartyCivility.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php similarity index 65% rename from src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdPartyCivility.php rename to src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php index 36325deaa..07f824cd1 100644 --- a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdPartyCivility.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php @@ -1,22 +1,17 @@ setName($val['name']) ->setActive(true); $manager->persist($civility); diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php index 6e3d1781b..26ce7415e 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php @@ -22,6 +22,8 @@ namespace Chill\MainBundle\DataFixtures\ORM; +use Chill\MainBundle\Doctrine\Model\Point; +use Chill\MainBundle\Entity\Country; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; @@ -40,21 +42,41 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface return 50; } - public static $refs = array(); + public static $refs = []; public function load(ObjectManager $manager) { - $lines = str_getcsv(self::$codes, "\n"); - $belgium = $manager->getRepository('ChillMainBundle:Country') - ->findOneBy(array('countryCode' => 'BE')); + echo "loading postal codes... \n"; + $this->loadPostalCodeCSV($manager, self::$postalCodeBelgium, 'BE'); + $this->loadPostalCodeCSV($manager, self::$postalCodeFrance, 'FR'); + } + + private function loadPostalCodeCSV(ObjectManager $manager, string $csv, string $countryCode) { + + $lines = str_getcsv($csv, "\n"); + $country = $manager->getRepository(Country::class) + ->findOneBy(['countryCode' => $countryCode]); foreach($lines as $line) { $code = str_getcsv($line); $c = new PostalCode(); - $c->setCountry($belgium) + $c->setCountry($country) ->setCode($code[0]) - ->setName(\ucwords(\strtolower($code[2]))) + ->setName(\ucwords(\strtolower($code[1]))) ; + + if (NULL != $code[3]){ + $c->setRefPostalCodeId($code[3]); + } + + if (NULL != $code[4] & NULL != $code[5]){ + $c->setCenter(Point::fromLonLat((float) $code[5], (float) $code[4])); + } + + if (NULL != $code[6]){ + $c->setPostalCodeSource($code[6]); + } + $manager->persist($c); $ref = 'postal_code_'.$code[0]; @@ -66,40 +88,342 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface $manager->flush(); } - - private static $codes = <<. */ -namespace Chill\ThirdPartyBundle\Entity; +namespace Chill\MainBundle\Entity; -use Chill\ThirdPartyBundle\Repository\ThirdPartyCivilityRepository; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation as Serializer; /** - * @ORM\Table(name="chill_3party.party_civility") - * @ORM\Entity(repositoryClass=ThirdPartyCivilityRepository::class) + * @ORM\Table(name="chill_main_civility") + * @ORM\Entity */ -class ThirdPartyCivility +class Civility { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Serializer\Groups({"read"}) */ private $id; /** * @ORM\Column(type="json") + * @Serializer\Groups({"read"}) */ - private $name = []; + private array $name = []; + + /** + * @ORM\Column(type="json") + * @Serializer\Groups({"read"}) + */ + private array $abbreviation = []; /** * @ORM\Column(type="boolean") */ - private $active = true; + private bool $active = true; public function getId(): ?int { @@ -76,4 +84,22 @@ class ThirdPartyCivility return $this; } + + /** + * @return array + */ + public function getAbbreviation(): array + { + return $this->abbreviation; + } + + /** + * @param array $abbreviation + * @return Civility + */ + public function setAbbreviation(array $abbreviation): self + { + $this->abbreviation = $abbreviation; + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/PostalCode.php b/src/Bundle/ChillMainBundle/Entity/PostalCode.php index 910245680..91ce46e17 100644 --- a/src/Bundle/ChillMainBundle/Entity/PostalCode.php +++ b/src/Bundle/ChillMainBundle/Entity/PostalCode.php @@ -2,6 +2,7 @@ namespace Chill\MainBundle\Entity; +use Chill\MainBundle\Doctrine\Model\Point; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; @@ -62,6 +63,30 @@ class PostalCode */ private $origin = 0; + /** + * @var string + * + * @ORM\Column(type="string", length=255, nullable=true) + * @groups({"read"}) + */ + private $refPostalCodeId; + + /** + * @var string + * + * @ORM\Column(type="string", length=255, nullable=true) + * @groups({"read"}) + */ + private $postalCodeSource; + + /** + * @var Point + * + * @ORM\Column(type="point", nullable=true) + * @groups({"read"}) + */ + private $center; + /** * Get id * @@ -169,5 +194,41 @@ class PostalCode { return $this->country; } + + public function getRefPostalCodeId(): ?string + { + return $this->refPostalCodeId; + } + + public function setRefPostalCodeId(?string $refPostalCodeId): self + { + $this->refPostalCodeId = $refPostalCodeId; + + return $this; + } + + public function getPostalCodeSource(): ?string + { + return $this->postalCodeSource; + } + + public function setPostalCodeSource(?string $postalCodeSource): self + { + $this->postalCodeSource = $postalCodeSource; + + return $this; + } + + public function getCenter(): ?Point + { + return $this->center; + } + + public function setCenter(?Point $center): self + { + $this->center = $center; + + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/CenterType.php b/src/Bundle/ChillMainBundle/Form/Type/CenterType.php deleted file mode 100644 index 14ac63e8c..000000000 --- a/src/Bundle/ChillMainBundle/Form/Type/CenterType.php +++ /dev/null @@ -1,136 +0,0 @@ - - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -namespace Chill\MainBundle\Form\Type; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Chill\MainBundle\Entity\Center; -use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; - -/** - * - * - * @author Julien Fastré - */ -class CenterType extends AbstractType -{ - /** - * The user linked with this type. - * - * @var \Chill\MainBundle\Entity\User - */ - protected $user; - - /** - * associative array where keys are center.id and - * value are center objects - * - * @var Center[] - */ - protected $reachableCenters = array(); - - /** - * - * @var CenterTransformer - */ - protected $transformer; - - public function __construct(TokenStorageInterface $tokenStorage, - CenterTransformer $transformer) - { - $this->user = $tokenStorage->getToken()->getUser(); - $this->transformer = $transformer; - $this->prepareReachableCenterByUser(); - } - - /** - * return a 'hidden' field if only one center is available. - * - * Return a 'choice' field if more than one center is available. - * - * @return string - * @throws \RuntimeException if the user is not associated with any center - */ - public function getParent() - { - $nbReachableCenters = count($this->reachableCenters); - - if ($nbReachableCenters <= 1) { - return HiddenType::class; - } else { - return EntityType::class; - } - } - - /** - * configure default options, i.e. add choices if user can reach multiple - * centers. - * - * @param OptionsResolver $resolver - */ - public function configureOptions(OptionsResolver $resolver) - { - if (count($this->reachableCenters) > 1) { - $resolver->setDefault('class', Center::class) - ->setDefault('choices', $this->reachableCenters) - ->setDefault('placeholder', 'Pick a center') - ; - } - - } - - /** - * add a data transformer if user can reach only one center - * - * @param FormBuilderInterface $builder - * @param array $options - */ - public function buildForm(FormBuilderInterface $builder, array $options) - { - if ($this->getParent() === HiddenType::class) { - $builder->addModelTransformer($this->transformer); - } - } - - /** - * populate reachableCenters as an associative array where - * keys are center.id and value are center entities. - * - */ - private function prepareReachableCenterByUser() - { - $groupCenters = $this->user->getGroupCenters(); - - foreach ($groupCenters as $groupCenter) { - - $center = $groupCenter->getCenter(); - - if (!array_key_exists($center->getId(), - $this->reachableCenters)) { - $this->reachableCenters[$center->getId()] = $center; - } - } - } - -} diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php index 1068522c9..2b20dbd98 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php @@ -24,14 +24,14 @@ use Symfony\Component\Form\FormInterface; /** * Available options : - * + * * - `button_add_label` * - `button_remove_label` * - `identifier`: an identifier to identify the kind of collecton. Useful if some * javascript should be launched associated to `add_entry`, `remove_entry` events. - * + * - `empty_collection_explain` + * * - * @author Julien Fastré */ class ChillCollectionType extends AbstractType { @@ -41,10 +41,11 @@ class ChillCollectionType extends AbstractType ->setDefaults([ 'button_add_label' => 'Add an entry', 'button_remove_label' => 'Remove entry', - 'identifier' => '' + 'identifier' => '', + 'empty_collection_explain' => '', ]); } - + public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['button_add_label'] = $options['button_add_label']; @@ -52,8 +53,9 @@ class ChillCollectionType extends AbstractType $view->vars['allow_delete'] = (int) $options['allow_delete']; $view->vars['allow_add'] = (int) $options['allow_add']; $view->vars['identifier'] = $options['identifier']; + $view->vars['empty_collection_explain'] = $options['empty_collection_explain']; } - + public function getParent() { return \Symfony\Component\Form\Extension\Core\Type\CollectionType::class; diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/CenterTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/CenterTransformer.php index b90ab5a46..c088edeba 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/CenterTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/CenterTransformer.php @@ -20,36 +20,57 @@ namespace Chill\MainBundle\Form\Type\DataTransformer; use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Repository\CenterRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; class CenterTransformer implements DataTransformerInterface { - private EntityManagerInterface $em; + private CenterRepository $centerRepository; + private bool $multiple = false; - public function __construct(EntityManagerInterface $em) - { - $this->em = $em; + public function __construct( + CenterRepository $centerRepository, + bool $multiple = false + ) { + $this->centerRepository = $centerRepository; + $this->multiple = $multiple; } public function reverseTransform($id) { if ($id === NULL) { - return NULL; + if ($this->multiple) { + return new ArrayCollection(); + } else { + return NULL; + } } - $center = $this - ->em - ->getRepository(Center::class) - ->find($id); + if ($this->multiple) { + $ids = \explode(',', $id); + } else { + $ids[] = (int) $id; + } - if ($center === NULL) { + $centers = $this + ->centerRepository + ->findBy(['id' => $ids ]); + + if ([] === $centers || count($ids) > count($centers)) { throw new TransformationFailedException(sprintf( - 'No center found with id %d', $id)); + 'No center found for one of those ids: %s', implode(',', $ids))); } - return $center; + if ($this->multiple) { + return new ArrayCollection($centers); + } else { + return $centers[0]; + } } public function transform($center) @@ -58,7 +79,21 @@ class CenterTransformer implements DataTransformerInterface return ''; } - return $center->getId(); - } + if ($this->multiple) { + if (!is_iterable($center)) { + throw new UnexpectedTypeException($center, \Traversable::class); + } + $ids = []; + foreach ($center as $c) { + $ids[] = $c->getId(); + } + return implode(',', $ids); + } else { + if (!$center instanceof Center) { + throw new UnexpectedTypeException($center, Center::class); + } + return (string) $center->getId(); + } + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php new file mode 100644 index 000000000..1068301b0 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php @@ -0,0 +1,55 @@ +requestStack = $requestStack; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + /** @var FilterOrderHelper $helper */ + $helper = $options['helper']; + + if ($helper->hasSearchBox()) { + $builder->add('q', SearchType::class, [ + 'label' => false, + 'required' => false + ]); + } + + foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) { + switch($key) { + case 'q': + continue; + case 'page': + $builder->add($key, HiddenType::class, [ + 'data' => 1 + ]); + break; + default: + $builder->add($key, HiddenType::class, [ + 'data' => $value + ]); + break; + } + } + } + + public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver) + { + $resolver->setRequired('helper') + ->setAllowedTypes('helper', FilterOrderHelper::class); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php b/src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php new file mode 100644 index 000000000..07c3d5c67 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php @@ -0,0 +1,174 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Chill\MainBundle\Repository\CenterRepository; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Security\Core\Security; + +/** + * Pick a center + * + * For a given role and, eventually, scopes, show a dropdown (if more than + * one reachable center) or a HiddenType (if one or zero center). + * + * + */ +class PickCenterType extends AbstractType +{ + protected AuthorizationHelperInterface $authorizationHelper; + + protected Security $security; + + protected CenterRepository $centerRepository; + + public function __construct( + AuthorizationHelperInterface $authorizationHelper, + Security $security, + CenterRepository $centerRepository + ) { + $this->authorizationHelper = $authorizationHelper; + $this->security = $security; + $this->centerRepository = $centerRepository; + } + + /** + * return a 'hidden' field if only one center is available. + * + * Return a 'choice' field if more than one center is available. + * + * @return string + * @throws \RuntimeException if the user is not associated with any center + */ + /* + public function getParent() + { + $nbReachableCenters = count($this->reachableCenters); + + if ($nbReachableCenters <= 1) { + return HiddenType::class; + } else { + return EntityType::class; + } + } + */ + + /** + * configure default options, i.e. add choices if user can reach multiple + * centers. + * + * @param OptionsResolver $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', Center::class) + ->setRequired('role') + ->setAllowedTypes('role', [ 'string' ]) + ->setDefault('scopes', []) + ->setAllowedTypes('scopes', ['iterable']) + ->setDefault('choice_options', []) + ; + /* + ->setDefault('choices', $this->reachableCenters) + ->setDefault('placeholder', 'Pick a center') + ; + */ + } + + /** + * add a data transformer if user can reach only one center + * + * @param FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $centers = $this->getReachableCenters($options['role'], $options['scopes']); + + if (count($centers) <= 1) { + $multiple = $options['choice_options']['multiple'] ?? false; + $builder->add('center', HiddenType::class); + $builder->get('center')->addModelTransformer( + new CenterTransformer($this->centerRepository, $multiple) + ); + } else { + $builder->add('center', EntityType::class, + \array_merge( + $options['choice_options'], + [ + 'class' => Center::class, + 'choices' => $centers + ] + ) + ); + } + + $builder + ->addModelTransformer(new CallbackTransformer( + function($data) { + if (NULL === $data) { + return ['center' => null]; + } + return ['center' => $data]; + }, + function($data) { + return $data['center']; + } + )); + } + + private function getReachableCenters(string $role, iterable $scopes): array + { + if (0 < count($scopes)) { + $centers = []; + + foreach($scopes as $scope) { + foreach ($this->authorizationHelper + ->getReachableCenters($this->security->getUser(), $role, $scope) as $center) { + $centers[spl_object_hash($center)] = $center; + } + } + + return \array_values($centers); + } else { + return $this->authorizationHelper + ->getReachableCenters($this->security->getUser(), $role); + } + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['is_hidden'] = count($this->getReachableCenters($options['role'], + $options['scopes'])) <= 1; + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyCivilityRepository.php b/src/Bundle/ChillMainBundle/Repository/CivilityRepository.php similarity index 65% rename from src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyCivilityRepository.php rename to src/Bundle/ChillMainBundle/Repository/CivilityRepository.php index 5c965a830..2702784f0 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyCivilityRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/CivilityRepository.php @@ -20,23 +20,23 @@ * along with this program. If not, see . */ -namespace Chill\ThirdPartyBundle\Repository; +namespace Chill\MainBundle\Repository; -use Chill\ThirdPartyBundle\Entity\ThirdPartyCivility; +use Chill\MainBundle\Entity\Civility; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; /** - * @method ThirdPartyCivility|null find($id, $lockMode = null, $lockVersion = null) - * @method ThirdPartyCivility|null findOneBy(array $criteria, array $orderBy = null) - * @method ThirdPartyCivility[] findAll() - * @method ThirdPartyCivility[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + * @method Civility|null find($id, $lockMode = null, $lockVersion = null) + * @method Civility|null findOneBy(array $criteria, array $orderBy = null) + * @method Civility[] findAll() + * @method Civility[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class ThirdPartyCivilityRepository extends ServiceEntityRepository +class CivilityRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { - parent::__construct($registry, ThirdPartyCivility::class); + parent::__construct($registry, Civility::class); } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss index 436f764dd..593cd49b4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss @@ -1,6 +1,9 @@ // Access to Bootstrap variables and mixins @import '~ChillMainAssets/module/bootstrap/shared'; +// Chill variables +@import './scss/chill_variables'; + // Chill mixins @import './scss/mixins'; diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/chill_variables.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/chill_variables.scss new file mode 100644 index 000000000..61d975b42 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/chill_variables.scss @@ -0,0 +1,13 @@ +/* +* ADDITIONNAL COLORS VARIABLES +*/ + +// Contexts banners +$chill-person-context: $chill-green; +$chill-accourse-context: #718596; +$chill-household-context: #929d69; + +// Badges colors +$social-issue-color: #4bafe8; +$social-action-color: $orange; +$activity-color: yellowgreen; diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss index 21ecde267..8e3a9121b 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss @@ -115,7 +115,7 @@ div.flex-table { } ul.record_actions { - align-self: flex-start; + align-self: flex-start; // !! conflict with .float-button.bottom see L297 } } } @@ -274,29 +274,35 @@ div.float-button { div.action { float: right; + position: relative; + z-index: 1; + } + + a { + word-break: break-all; // break email address } } &.top { div.action { padding: 0 0 1em 1em; } - - // avoid a position relative that make links unclickable - .fa-ul > li { - position: initial; - } } &.bottom { display: flex; overflow: hidden; + div.action { height: calc(100% - 0em); - shape-outside: inset(calc(100% - 2em) 0 0); + shape-outside: inset(calc(100% - 4em) 0 0); display: flex; align-items: flex-end; padding: 0 0 0 1em; * { - align-self: flex-end !important; + align-self: flex-end !important; // neutralize flex-table rule L118 + } + ul.record_actions { + margin: 0; + padding: 0; } } } @@ -304,8 +310,19 @@ div.float-button { padding: 1em; border: 1px solid black; background-color: yellow; - div.action { - background-color: transparentize(#00ffff, 0.4); + div.box { + div.action { + background-color: transparentize(cyan, 0.4); + } + & > ul li, + & > div, + & > p { + background-color: transparentize(orange, 0.4); + } + & > div { + margin-bottom: 1em; + } } } + } diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/mixins.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/mixins.scss index 9b4650087..ef874c274 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/mixins.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/mixins.scss @@ -1,4 +1,3 @@ - // // Titles in forms // @@ -12,7 +11,6 @@ display: block; } - // We use box-shadow instead of border // to avoid to manage border double-width // Then we can simulate border-collapse: collapse (table) diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/collection/index.js b/src/Bundle/ChillMainBundle/Resources/public/lib/collection/index.js index 0a4955207..b763c4ddb 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/collection/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/collection/index.js @@ -1,27 +1,27 @@ /** * Javascript file which handle ChillCollectionType - * - * Two events are emitted by this module, both on window and on collection / ul. - * + * + * Two events are emitted by this module, both on window and on collection / ul. + * * Collection (an UL element) and entry (a li element) are associated with those * events. - * + * * ``` * window.addEventListener('collection-add-entry', function(e) { * console.log(e.detail.collection); * console.log(e.detail.entry); * }); - * + * * window.addEventListener('collection-remove-entry', function(e) { * console.log(e.detail.collection); * console.log(e.detail.entry); * }); - * + * * collection.addEventListener('collection-add-entry', function(e) { * console.log(e.detail.collection); * console.log(e.detail.entry); * }); - * + * * collection.addEventListener('collection-remove-entry', function(e) { * console.log(e.detail.collection); * console.log(e.detail.entry); @@ -38,7 +38,7 @@ class CollectionEvent { } /** - * + * * @param {type} button * @returns {handleAdd} */ @@ -47,6 +47,7 @@ var handleAdd = function(button) { form_name = button.dataset.collectionAddTarget, prototype = button.dataset.formPrototype, collection = document.querySelector('ul[data-collection-name="'+form_name+'"]'), + empty_explain = collection.querySelector('li[data-collection-empty-explain]'), entry = document.createElement('li'), event = new CustomEvent('collection-add-entry', { detail: { collection: collection, entry: entry } }), counter = collection.childNodes.length, @@ -56,8 +57,11 @@ var handleAdd = function(button) { entry.innerHTML = content; entry.classList.add('entry'); initializeRemove(collection, entry); + if (empty_explain !== null) { + empty_explain.remove(); + } collection.appendChild(entry); - + collection.dispatchEvent(event); window.dispatchEvent(event); }; @@ -70,30 +74,30 @@ var initializeRemove = function(collection, entry) { allowDelete = collection.dataset.collectionAllowDelete, event = new CustomEvent('collection-remove-entry', { detail: { collection: collection, entry: entry } }) ; - + if (allowDelete === '0' && isPersisted === '1') { return; } - + button.classList.add('btn', 'btn-delete', 'remove-entry'); button.textContent = content; - + button.addEventListener('click', function(e) { e.preventDefault(); entry.remove(); collection.dispatchEvent(event); window.dispatchEvent(event); }); - + entry.appendChild(button); }; window.addEventListener('load', function() { - var + var addButtons = document.querySelectorAll("button[data-collection-add-target]"), collections = document.querySelectorAll("ul[data-collection-name]") ; - + for (let i = 0; i < addButtons.length; i ++) { let addButton = addButtons[i]; addButton.addEventListener('click', function(e) { @@ -101,11 +105,15 @@ window.addEventListener('load', function() { handleAdd(e.target); }); } - + for (let i = 0; i < collections.length; i ++) { let entries = collections[i].querySelectorAll(':scope > li'); - + for (let j = 0; j < entries.length; j ++) { + console.log(entries[j].dataset); + if (entries[j].dataset.collectionEmptyExplain === "1") { + continue; + } initializeRemove(collections[i], entries[j]); } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/disable-buttons/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/disable-buttons/index.js new file mode 100644 index 000000000..c6ec7c789 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/module/disable-buttons/index.js @@ -0,0 +1,16 @@ +/** + * Submit button(s) are disabled as soon as submit button is clicked and form is submitted. + * ID 'create-form' must be added to submit forms. + */ + +var form = document.getElementById('create-form'); +var submitButtons = document.querySelectorAll("[type=submit]"); + +form.addEventListener('submit', function(e){ + for(var i=0; i +
{% block crud_content_header %}

{{ ('crud.'~crud_name~'.title_edit')|trans }}

{% endblock crud_content_header %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/CRUD/_index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/CRUD/_index.html.twig index 46828a49e..15774bdf6 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/CRUD/_index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/CRUD/_index.html.twig @@ -1,9 +1,15 @@
- + {% block index_header %}

{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}

{% endblock index_header %} +{% block filter_order %} + {% if filter_order is not null %} + {{ filter_order|chill_render_filter_order_helper }} + {% endif %} +{% endblock %} + {% if entities|length == 0 %} {% block no_existing_entities %}

{{ no_existing_entities_sentences|default('No entities')|trans }}

@@ -32,17 +38,20 @@ {% endif %} -
- {{ chill_pagination(paginator) }} -
+{% block pagination %} +
+ {{ chill_pagination(paginator) }} +
+{% endblock %} {% block list_actions %} {% endblock list_actions %}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/CRUD/_new_content.html.twig b/src/Bundle/ChillMainBundle/Resources/views/CRUD/_new_content.html.twig index 4c2003617..906a77d0d 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/CRUD/_new_content.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/CRUD/_new_content.html.twig @@ -1,10 +1,11 @@ +{% set formId = crudMainFormId|default('crud_main_form') %}
{% block crud_content_header %}

{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}

{% endblock crud_content_header %} {% block crud_content_form %} - {{ form_start(form) }} + {{ form_start(form, { 'attr' : { 'id': formId } }) }} {% block crud_content_form_rows %} {% for f in form %}{% if f.vars.name != 'submit' %} @@ -14,6 +15,8 @@ {% block crud_content_after_form %}{% endblock %} + {{ form_end(form) }} + {% block crud_content_form_actions %}
    {% block content_form_actions_back %} @@ -25,21 +28,21 @@ {% endblock %} {% block content_form_actions_save_and_close %}
  • -
  • {% endblock %} {% block content_form_actions_save_and_show %}
  • -
  • {% endblock %} {% block content_form_actions_save_and_new %}
  • -
  • diff --git a/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig b/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig new file mode 100644 index 000000000..074f3bb94 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig @@ -0,0 +1,12 @@ +{{ form_start(form) }} +
    +
    +
    +
    + {{ form_widget(form.q)}} + +
    +
    +
    +
    +{{ form_end(form) }} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig index ce406286b..1799059e4 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig @@ -168,6 +168,10 @@ {{ form_widget(entry) }}
+ {% else %} +
  • + {{ form.vars.empty_collection_explain|default('No item')|trans }} +
  • {% endfor %} @@ -198,3 +202,15 @@ {{ form_widget(entry) }} {% endfor %} {% endblock comment_widget %} + +{% block pick_center_widget %} + {{ form_widget(form.center) }} +{% endblock pick_center_widget %} + +{% block pick_center_row %} + {% if (not form.vars.is_hidden) %} + {{ block('form_row') }} + {% else %} + {{ form_widget(form.center) }} + {% endif %} +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Menu/defaultMenu.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Menu/defaultMenu.html.twig index 67a32b98b..6eada4dd6 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Menu/defaultMenu.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Menu/defaultMenu.html.twig @@ -1,5 +1,5 @@ {# - * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, + * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, / * * This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ #} \ No newline at end of file + diff --git a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig index 27fc4430b..8bee2983e 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig @@ -70,6 +70,9 @@
    + {{ chill_widget('homepage', {} ) }} {% endblock %} diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php index 2b223a0fe..afff931ed 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php @@ -41,9 +41,8 @@ use Chill\MainBundle\Entity\RoleScope; * * Provides methods for user and entities information. * - * @author Julien Fastré */ -class AuthorizationHelper +class AuthorizationHelper implements AuthorizationHelperInterface { protected RoleHierarchyInterface $roleHierarchy; @@ -203,9 +202,9 @@ class AuthorizationHelper * @param User $user * @param string|Role $role * @param null|Scope $scope - * @return Center[] + * @return Center[]|array */ - public function getReachableCenters(User $user, $role, Scope $scope = null) + public function getReachableCenters(User $user, string $role, ?Scope $scope = null): array { if ($role instanceof Role) { $role = $role->getRole(); @@ -267,9 +266,9 @@ class AuthorizationHelper * @param User $user * @param string role * @param Center|Center[] $center - * @return Scope[] + * @return Scope[]|array */ - public function getReachableScopes(User $user, $role, $center) + public function getReachableScopes(User $user, string $role, $center): array { if ($role instanceof Role) { $role = $role->getRole(); diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelperInterface.php b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelperInterface.php new file mode 100644 index 000000000..31709bcd5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelperInterface.php @@ -0,0 +1,32 @@ +formFactory = $formFactory; + $this->requestStack = $requestStack; + } + + public function setSearchBox($searchBoxFields = null): self + { + $this->searchBoxFields = $searchBoxFields; + + return $this; + } + + public function hasSearchBox(): bool + { + return $this->searchBoxFields !== null; + } + + private function getFormData(): array + { + return [ + 'q' => $this->getQueryString() + ]; + } + + public function getQueryString(): ?string + { + $q = $this->requestStack->getCurrentRequest() + ->query->get('q', null); + + return empty($q) ? NULL : $q; + } + + public function buildForm($name = null, string $type = FilterOrderType::class, array $options = []): FormInterface + { + return $this->formFactory + ->createNamed($name, $type, $this->getFormData(), \array_merge([ + 'helper' => $this, + 'method' => 'GET', + 'csrf_protection' => false, + ], $options)); + } +} diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php new file mode 100644 index 000000000..e1df09827 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php @@ -0,0 +1,40 @@ +formFactory = $formFactory; + $this->requestStack = $requestStack; + } + + public function addSearchBox(array $fields, ?array $options = []): self + { + $this->searchBoxFields = $fields; + + return $this; + } + + public function build(): FilterOrderHelper + { + $helper = new FilterOrderHelper( + $this->formFactory, + $this->requestStack + ); + + $helper->setSearchBox($this->searchBoxFields); + + return $helper; + } +} diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php new file mode 100644 index 000000000..1b1c4c983 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php @@ -0,0 +1,26 @@ +formFactory = $formFactory; + $this->requestStack = $requestStack; + } + + public function create(string $context, ?array $options = []): FilterOrderHelperBuilder + { + return new FilterOrderHelperBuilder($this->formFactory, $this->requestStack); + } +} diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactoryInterface.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactoryInterface.php new file mode 100644 index 000000000..a222adf7a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactoryInterface.php @@ -0,0 +1,8 @@ + true, 'is_safe' => ['html'], + ]) + ]; + } + + public function renderFilterOrderHelper( + Environment $environment, + FilterOrderHelper $helper, + ?string $template = '@ChillMain/FilterOrder/base.html.twig', + ?array $options = [] + ) { + return $environment->render($template, [ + 'helper' => $helper, + 'form' => $helper->buildForm()->createView(), + 'options' => $options + ]); + } + +} diff --git a/src/Bundle/ChillMainBundle/Tests/Form/Type/CenterTypeTest.php b/src/Bundle/ChillMainBundle/Tests/Form/Type/PickCenterTypeTest.php similarity index 91% rename from src/Bundle/ChillMainBundle/Tests/Form/Type/CenterTypeTest.php rename to src/Bundle/ChillMainBundle/Tests/Form/Type/PickCenterTypeTest.php index 48b96375e..a72360301 100644 --- a/src/Bundle/ChillMainBundle/Tests/Form/Type/CenterTypeTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Form/Type/PickCenterTypeTest.php @@ -28,18 +28,19 @@ use Symfony\Bridge\Doctrine\Form\Type\EntityType; /** - * + * * * @author Julien Fastré */ class CenterTypeTest extends TypeTestCase { /** - * Test that a user which can reach only one center + * Test that a user which can reach only one center * render as an hidden field */ public function testUserCanReachSingleCenter() { + $this->markTestSkipped(); //prepare user $center = $this->prepareCenter(1, 'center'); $groupCenter = (new GroupCenter()) @@ -47,18 +48,19 @@ class CenterTypeTest extends TypeTestCase ; $user = (new User()) ->addGroupCenter($groupCenter); - + $type = $this->prepareType($user); - + $this->assertEquals(HiddenType::class, $type->getParent()); } - + /** - * Test that a user which can reach only one center + * Test that a user which can reach only one center * render as an hidden field */ public function testUserCanReachMultipleSameCenter() { + $this->markTestSkipped(); //prepare user $center = $this->prepareCenter(1, 'center'); $groupCenterA = (new GroupCenter()) @@ -70,18 +72,19 @@ class CenterTypeTest extends TypeTestCase $user = (new User()) ->addGroupCenter($groupCenterA) ->addGroupCenter($groupCenterB); - + $type = $this->prepareType($user); - + $this->assertEquals(HiddenType::class, $type->getParent()); } - + /** - * Test that a user which can reach multiple center + * Test that a user which can reach multiple center * make CenterType render as "entity" type. */ public function testUserCanReachMultipleCenters() { + $this->markTestSkipped(); //prepare user $centerA = $this->prepareCenter(1, 'centerA'); $centerB = $this->prepareCenter(2, 'centerB'); @@ -95,61 +98,61 @@ class CenterTypeTest extends TypeTestCase ->addGroupCenter($groupCenterA) ->addGroupCenter($groupCenterB) ; - + $type = $this->prepareType($user); - + $this->assertEquals(EntityType::class, $type->getParent()); } - + /** * prepare a mocked center, with and id and name given - * + * * @param int $id * @param string $name - * @return \Chill\MainBundle\Entity\Center + * @return \Chill\MainBundle\Entity\Center */ private function prepareCenter($id, $name) { $prophet = new \Prophecy\Prophet; - + $prophecyCenter = $prophet->prophesize(); $prophecyCenter->willExtend('\Chill\MainBundle\Entity\Center'); $prophecyCenter->getId()->willReturn($id); $prophecyCenter->getName()->willReturn($name); - + return $prophecyCenter->reveal(); } - - + + /** * prepare the type with mocked center transformer and token storage - * + * * @param User $user the user for wich the form will be prepared * @return CenterType */ private function prepareType(User $user) { - $prophet = new \Prophecy\Prophet; - + $prophet = new \Prophecy\Prophet; + //create a center transformer $centerTransformerProphecy = $prophet->prophesize(); $centerTransformerProphecy ->willExtend('Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer'); $transformer = $centerTransformerProphecy->reveal(); - + $tokenProphecy = $prophet->prophesize(); $tokenProphecy ->willImplement('\Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); $tokenProphecy->getUser()->willReturn($user); $token = $tokenProphecy->reveal(); - + $tokenStorageProphecy = $prophet->prophesize(); $tokenStorageProphecy ->willExtend('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage'); $tokenStorageProphecy->getToken()->willReturn($token); $tokenStorage = $tokenStorageProphecy->reveal(); - + return new CenterType($tokenStorage, $transformer); } - + } diff --git a/src/Bundle/ChillMainBundle/Tests/Services/ChillMenuTwigFunctionTest.php b/src/Bundle/ChillMainBundle/Tests/Services/ChillMenuTwigFunctionTest.php deleted file mode 100644 index a85ff9710..000000000 --- a/src/Bundle/ChillMainBundle/Tests/Services/ChillMenuTwigFunctionTest.php +++ /dev/null @@ -1,54 +0,0 @@ - - * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -namespace Chill\MainBundle\Tests\Services; - -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\DomCrawler\Crawler; - -/** - * Test the Twig function 'chill_menu' - * - * @author Julien Fastré - */ -class ChillMenuTwigFunctionTest extends KernelTestCase -{ - - private static $templating; - - public static function setUpBeforeClass() - { - self::bootKernel(array('environment' => 'test')); - static::$templating = static::$container - ->get('templating'); - $pathToBundle = static::$container->getParameter('kernel.bundles_metadata')['ChillMainBundle']['path']; - //load templates in Tests/Resources/views - static::$container->get('twig.loader') - ->addPath($pathToBundle.'/Resources/test/views/', $namespace = 'tests'); - } - - public function testNormalMenu() - { - $content = static::$templating->render('@tests/menus/normalMenu.html.twig'); - $this->assertContains('ul', $content, - "test that the file contains an ul tag" - ); - } -} diff --git a/src/Bundle/ChillMainBundle/chill.webpack.config.js b/src/Bundle/ChillMainBundle/chill.webpack.config.js index 0204166d0..79c8949cd 100644 --- a/src/Bundle/ChillMainBundle/chill.webpack.config.js +++ b/src/Bundle/ChillMainBundle/chill.webpack.config.js @@ -58,6 +58,7 @@ module.exports = function(encore, entries) encore.addEntry('mod_forkawesome', __dirname + '/Resources/public/module/forkawesome/index.js'); encore.addEntry('mod_bootstrap', __dirname + '/Resources/public/module/bootstrap/index.js'); encore.addEntry('mod_ckeditor5', __dirname + '/Resources/public/module/ckeditor5/index.js'); + encore.addEntry('mod_disablebuttons', __dirname + '/Resources/public/module/disable-buttons/index.js'); // Vue entrypoints encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js'); diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index 53e602998..6540069fa 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -60,11 +60,6 @@ services: tags: - { name: twig.extension } - chill.main.form.data_transformer.center_transformer: - class: Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer - arguments: - - "@doctrine.orm.entity_manager" - chill.main.validator.role_scope_scope_presence: class: Chill\MainBundle\Validation\Validator\RoleScopeScopePresence arguments: diff --git a/src/Bundle/ChillMainBundle/config/services/form.yaml b/src/Bundle/ChillMainBundle/config/services/form.yaml index f719edb55..b28ced053 100644 --- a/src/Bundle/ChillMainBundle/config/services/form.yaml +++ b/src/Bundle/ChillMainBundle/config/services/form.yaml @@ -36,13 +36,9 @@ services: tags: - { name: form.type, alias: select2_chill_language } - chill.main.form.type.center: - class: Chill\MainBundle\Form\Type\CenterType - arguments: - - "@security.token_storage" - - "@chill.main.form.data_transformer.center_transformer" - tags: - - { name: form.type, alias: center } + Chill\MainBundle\Form\Type\PickCenterType: + autowire: true + autoconfigure: true chill.main.form.type.composed_role_scope: class: Chill\MainBundle\Form\Type\ComposedRoleScopeType @@ -97,6 +93,10 @@ services: arguments: - '@Chill\MainBundle\Export\ExportManager' + Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer: + autowire: true + autoconfigure: true + chill.main.form.advanced_search_type: class: Chill\MainBundle\Form\AdvancedSearchType autowire: true diff --git a/src/Bundle/ChillMainBundle/config/services/security.yaml b/src/Bundle/ChillMainBundle/config/services/security.yaml index 0d820220b..15d6f7da5 100644 --- a/src/Bundle/ChillMainBundle/config/services/security.yaml +++ b/src/Bundle/ChillMainBundle/config/services/security.yaml @@ -38,6 +38,7 @@ services: autowire: true autoconfigure: true Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper' + Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface: '@chill.main.security.authorization.helper' chill.main.role_provider: class: Chill\MainBundle\Security\RoleProvider diff --git a/src/Bundle/ChillMainBundle/config/services/templating.yaml b/src/Bundle/ChillMainBundle/config/services/templating.yaml index cd35a6466..e264f3a99 100644 --- a/src/Bundle/ChillMainBundle/config/services/templating.yaml +++ b/src/Bundle/ChillMainBundle/config/services/templating.yaml @@ -36,7 +36,7 @@ services: autowire: true tags: - { name: 'chill.render_entity' } - + Chill\MainBundle\Templating\ChillMarkdownRenderExtension: tags: - { name: twig.extension } @@ -46,4 +46,10 @@ services: - '@Symfony\Component\Templating\EngineInterface' tags: - { name: 'chill.render_entity' } - + + Chill\MainBundle\Templating\Listing\: + resource: './../../Templating/Listing' + autoconfigure: true + autowire: true + + Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface: '@Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory' diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211006151653.php b/src/Bundle/ChillMainBundle/migrations/Version20211006151653.php new file mode 100644 index 000000000..9f4508ba8 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211006151653.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE chill_main_postal_code ADD refPostalCodeId VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_postal_code ADD postalCodeSource VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_postal_code ADD center geometry(POINT,4326) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_postal_code DROP refPostalCodeId'); + $this->addSql('ALTER TABLE chill_main_postal_code DROP postalCodeSource'); + $this->addSql('ALTER TABLE chill_main_postal_code DROP center'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php b/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php new file mode 100644 index 000000000..b81c7f74e --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php @@ -0,0 +1,31 @@ +addSql('CREATE SEQUENCE chill_main_civility_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_main_civility (id INT NOT NULL, name JSON NOT NULL, abbreviation JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP SEQUENCE chill_main_civility_id_seq'); + $this->addSql('DROP TABLE chill_main_civility'); + } +} diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index d99a565c1..a9ca52593 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -1,7 +1,7 @@ "This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License": "Ce programme est un logiciel libre: vous pouvez le redistribuer et/ou le modifier selon les termes de la licence GNU Affero GPL" User manual: Manuel d'utilisation Search: Rechercher -'Search persons, ...': 'Recherche des personnes, ...' +"Search persons, ...": "Recherche des personnes, ..." Person name: Nom / Prénom de la personne Login: Connexion Logout: Se déconnecter @@ -33,7 +33,7 @@ Cancel: Annuler Save: Enregistrer This form contains errors: Ce formulaire contient des erreurs Choose an user: Choisir un utilisateur -'You are going to leave a page with unsubmitted data. Are you sure you want to leave ?': "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?" +"You are going to leave a page with unsubmitted data. Are you sure you want to leave ?": "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?" No value: Aucune information Last updated by: Dernière mise à jour par Last updated on: Dernière mise à jour le @@ -56,6 +56,7 @@ centers: centres Centers: Centres comment: commentaire Comment: Commentaire +Any comment: Aucun commentaire # comment embeddable No comment associated: Aucun commentaire @@ -71,24 +72,24 @@ Postal code: Code postal Valid from: Valide à partir du Choose a postal code: Choisir un code postal address: - address_homeless: L'adresse est-elle celle d'un domicile fixe ? - real address: Adresse d'un domicile - consider homeless: N'est pas l'adresse d'un domicile (SDF) + address_homeless: L'adresse est-elle celle d'un domicile fixe ? + real address: Adresse d'un domicile + consider homeless: N'est pas l'adresse d'un domicile (SDF) address more: - floor: ét - corridor: coul - steps: esc - flat: appart - buildingName: résidence - extra: '' - distribution: cedex + floor: ét + corridor: coul + steps: esc + flat: appart + buildingName: résidence + extra: "" + distribution: cedex Create a new address: Créer une nouvelle adresse #serach Your search is empty. Please provide search terms.: La recherche est vide. Merci de fournir des termes de recherche. The domain %domain% is unknow. Please check your search.: Le domaine de recherche "%domain%" est inconnu. Merci de vérifier votre recherche. -Invalid terms : Recherche invalide -You should not have more than one domain. : Vous ne devriez pas avoir plus d'un domaine de recherche. +Invalid terms: Recherche invalide +You should not have more than one domain.: Vous ne devriez pas avoir plus d'un domaine de recherche. #used for page title Search %pattern%: Recherche de "%pattern%" Results %start%-%end% of %total%: Résultats %start%-%end% sur %total% @@ -113,9 +114,9 @@ Permissions Menu: Gestion des droits Permissions management of your chill installation: Gestion des permissions de votre instance #admin section -'Administration interface': Interface d'administration +"Administration interface": Interface d'administration Welcome to the admin section !: > - Bienvenue dans l'interface d'administration ! + Bienvenue dans l'interface d'administration ! #admin section for center's administration Create a new center: Créer un nouveau centre @@ -210,7 +211,6 @@ Problem during download: Problème durant le téléchargement # sans valeur without data: sans valeur - #CSV List Formatter Add a number on first column: La première colonne est un numéro Number: Numéro @@ -228,9 +228,9 @@ Comma separated values (CSV): Valeurs séparées par des virgules (CSV - tableur Choose the format: Choisir le format # select2 -'select2.no_results': Aucun résultat -'select2.error_loading': Erreur de chargement des résultats -'select2.searching': Recherche en cours... +"select2.no_results": Aucun résultat +"select2.error_loading": Erreur de chargement des résultats +"select2.searching": Recherche en cours... # change password Change my password: Modification du mot de passe @@ -258,38 +258,35 @@ Impersonate: Incarner l'utilisateur Impersonate mode: Mode fantôme crud: - # general items - new: - button_action_form: Créer - link_edit: Modifier - save_and_close: Créer & fermer - save_and_show: Créer & voir - save_and_new: Créer & nouveau - success: Les données ont été créées - edit: - button_action_form: Enregistrer - back_to_view: Voir - save_and_close: Enregistrer & fermer - save_and_show: Enregistrer & voir - success: Les données ont été modifiées - delete: - success: Les données ont été supprimées - link_to_form: Supprimer - default: - success: Les données ont été enregistrées - view: - link_duplicate: Dupliquer - ## admin for users - admin_user: - index: - title: Utilisateurs - add_new: "Créer" - is_active: "Actif ?" - usernames: "Identifiants" - mains: "Champs principaux" - title_new: "Nouvel utilisateur" - title_edit: Modifier un utilisateur + # general items + new: + button_action_form: Créer + link_edit: Modifier + save_and_close: Créer & fermer + save_and_show: Créer & voir + save_and_new: Créer & nouveau + success: Les données ont été créées + edit: + button_action_form: Enregistrer + back_to_view: Voir + save_and_close: Enregistrer & fermer + save_and_show: Enregistrer & voir + success: Les données ont été modifiées + delete: + success: Les données ont été supprimées + link_to_form: Supprimer + default: + success: Les données ont été enregistrées + view: + link_duplicate: Dupliquer No entities: Aucun élément CHILL_FOO_SEE: Voir un élément CHILL_FOO_EDIT: Modifier un élément + +#Show templates +Date: Date +By: Par +For: Pour +Created for: Créé pour +Created by: Créé par diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonController.php b/src/Bundle/ChillPersonBundle/Controller/PersonController.php index f9190857c..a7249dda0 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonController.php @@ -268,7 +268,7 @@ final class PersonController extends AbstractController ) { $this->em->persist($person); - $this->em->flush(); + // $this->em->flush(); $this->lastPostDataReset(); if ($form->get('createPeriod')->isClicked()) { diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index d597e3572..bc3b798fa 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -84,6 +84,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/repository.yaml'); $loader->load('services/serializer.yaml'); $loader->load('services/security.yaml'); + $loader->load('services/doctrineEventListener.yaml'); // load service advanced search only if configure if ($config['search']['search_by_phone'] != 'never') { diff --git a/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php b/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php new file mode 100644 index 000000000..6c9b746d7 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php @@ -0,0 +1,39 @@ +getObject() instanceof Person){ + + $person = $event->getObject(); + $firstnameCaps = mb_convert_case(mb_strtolower($person->getFirstName()), MB_CASE_TITLE, 'UTF-8'); + $firstnameCaps = ucwords(strtolower($firstnameCaps), " \t\r\n\f\v'-"); + $person->setFirstName($firstnameCaps); + + $lastnameCaps = mb_strtoupper($person->getLastName(), 'UTF-8'); + $person->setLastName($lastnameCaps); + + } elseif ($event->getObject() instanceof PersonAltName){ + + $altname = $event->getObject(); + $altnameCaps = mb_strtoupper($altname->getLabel(), 'UTF-8'); + $altname->setLabel($altnameCaps); + + } else { + + throw new LogicException('Entity must be a person or an altname'); + + } + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php b/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php index 36d088c25..8fa95fe4f 100644 --- a/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php @@ -22,7 +22,9 @@ namespace Chill\PersonBundle\Form; use Chill\MainBundle\Form\Event\CustomizeFormEvent; +use Chill\MainBundle\Repository\CenterRepository; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Security\Authorization\PersonVoter; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -30,12 +32,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Chill\MainBundle\Form\Type\ChillDateType; -use Chill\MainBundle\Form\Type\CenterType; +use Chill\MainBundle\Form\Type\PickCenterType; use Chill\PersonBundle\Form\Type\GenderType; use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Form\Type\PersonAltNameType; -use Chill\MainBundle\Form\Type\Export\PickCenterType; final class CreationPersonType extends AbstractType { @@ -43,11 +44,7 @@ final class CreationPersonType extends AbstractType // TODO: See if this is still valid and update accordingly. const NAME = 'chill_personbundle_person_creation'; - /** - * - * @var CenterTransformer - */ - private $centerTransformer; + private CenterRepository $centerRepository; /** * @@ -58,11 +55,11 @@ final class CreationPersonType extends AbstractType private EventDispatcherInterface $dispatcher; public function __construct( - CenterTransformer $centerTransformer, + CenterRepository $centerRepository, ConfigPersonAltNamesHelper $configPersonAltNamesHelper, EventDispatcherInterface $dispatcher ) { - $this->centerTransformer = $centerTransformer; + $this->centerTransformer = $centerRepository; $this->configPersonAltNamesHelper = $configPersonAltNamesHelper; $this->dispatcher = $dispatcher; } @@ -82,8 +79,9 @@ final class CreationPersonType extends AbstractType ->add('gender', GenderType::class, array( 'required' => true, 'placeholder' => null )) - ->add('center', CenterType::class, [ - 'required' => false + ->add('center', PickCenterType::class, [ + 'required' => false, + 'role' => PersonVoter::CREATE, ]) ; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss index 094a2eb0a..2e5821659 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss @@ -1,10 +1,13 @@ // Access to Bootstrap variables and mixins @import '~ChillMainAssets/module/bootstrap/shared'; +@import 'ChillMainAssets/chill/scss/chill_variables'; + // Complete/override Main generic assets @import './scss/mixins'; @import './scss/render_box.scss'; @import './scss/flex_table.scss'; +@import './scss/badge.scss'; // Specific templates styles @import './scss/accompanying_period_work.scss'; @@ -19,13 +22,13 @@ div.banner { div#header-person-name { - background: none repeat scroll 0 0 $chill-green-dark; + background: none repeat scroll 0 0 shade-color($chill-person-context, 20%); color: $white; padding-top: 1em; padding-bottom: 1em; } div#header-person-details { - background: none repeat scroll 0 0 $chill-green; + background: none repeat scroll 0 0 $chill-person-context; color: $white; padding-top: 1em; padding-bottom: 1em; @@ -92,7 +95,6 @@ div.person-view { * Header custom for Accompanying Course */ -$chill-accourse-context: #718596; div.banner { div#header-accompanying_course-name { @@ -104,9 +106,9 @@ div.banner { span { a { color: $white; - } - a:hover { - text-decoration: underline; + &:hover { + text-decoration: underline; + } } } } @@ -118,19 +120,11 @@ div.banner { } } -abbr.referrer { - font-size: 70%; - padding-right: 0.4em; - align-self: center; // in flex context -} - /* * HOUSEHOLD CONTEXT * Header custom for Household */ -$chill-household-context: #929d69; - div.banner { div#header-household-name { background: none repeat scroll 0 0 $chill-household-context; @@ -142,9 +136,9 @@ div.banner { span { a { color: $white; - } - a:hover { - text-decoration: underline; + &:hover { + text-decoration: underline; + } } } div.household-members { @@ -185,6 +179,7 @@ div.banner { } } +/// div.household-resume { display: flex; flex-direction: row; @@ -208,48 +203,10 @@ div.household-resume { } } -/* -* BADGES, MARKS, PINS -* for chill person theme -*/ - -// chill person badges -span.badge-person, -span.badge-thirdparty { - display: inline-block; - padding: 0 0.5em !important; - background-color: $white; - color: $dark; - border: 1px solid $chill-ll-gray; - border-bottom-width: 2px; - border-bottom-style: solid; - border-radius: 6px; - a { - text-decoration: none; - } -} -span.badge-person { - border-bottom-color: $chill-green; -} -// todo: move in thirdparty -span.badge-thirdparty { - border-bottom-color: shade-color($chill-pink, 10%); -} - -// household holder mark -span.fa-holder { - width: 1em; - margin: -10px 0.3em -8px 0; - i:last-child { - font-weight: 900; - color: white; - font-size: 70%; - font-family: "Open Sans Extrabold"; - } -} - +/// Horizontal list of persons (Accourse resume page) div.accompanyingcourse-resume { div.associated-persons { + font-size: 110%; span.household { display: inline-block; border-radius: 8px; @@ -272,3 +229,16 @@ div.accompanyingcourse-resume { } } } + +/// +abbr.referrer { // still used ? + font-size: 70%; + padding-right: 0.4em; + align-self: center; // in flex context +} + +.updatedBy { + margin-top: 0.3rem; + font-size: 0.9rem; + font-style: italic; +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss index e4d84a7a4..e6bdb174b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss @@ -1,31 +1,6 @@ /// AccompanyingCourse Work list Page div.accompanying_course_work-list { - h2.title { - display: flex; - flex-direction: row; - width: 100%; - color: $dark; - //height: 40px; - - span.title_label { - border-radius: 0.35rem 0 0 0.35rem; - background-color: $social-action-label-color; - color: $white; - font-size: 80%; - padding: 0.5em; - padding-right: 0; - } - span.title_action { - flex-grow: 1; - margin: 0 0 0 auto; - border-radius: 0 0.35rem 0.35rem 0; - background-color: $light; - padding: 0.2em 0.7em; - @include badge_social_action; - } - } - div.timeline { width: 100%; ul { @@ -125,7 +100,7 @@ div.accompanying_course_work-list { } } &.goal_title li::marker { - color: $sky-blue; + color: $social-issue-color; } &.result_list li::marker { color: $pink; @@ -133,11 +108,4 @@ div.accompanying_course_work-list { } } - .updatedBy { - margin-top: 0.3rem; - font-size: 0.9rem; - font-style: italic; - } - } - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss new file mode 100644 index 000000000..7e9f13149 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss @@ -0,0 +1,119 @@ +/* +* BADGES PERSON AND THIRDPARTY +*/ + +span.badge-person, +span.badge-thirdparty { + display: inline-block; + padding: 0 0.5em !important; + background-color: $white; + color: $dark; + border: 1px solid $chill-ll-gray; + border-bottom-width: 2px; + border-bottom-style: solid; + border-radius: 6px; + a { + text-decoration: none; + } +} +span.badge-person { + border-bottom-color: $chill-green; +} +span.badge-thirdparty { + border-bottom-color: shade-color($chill-pink, 10%); +} + +/* +* HOUSEHOLD HOLDER MARK +*/ + +span.fa-holder { + width: 1em; + margin: -10px 0.3em -8px 0; + i:last-child { + font-family: "Open Sans Extrabold"; + font-weight: 900; + font-size: 70%; + color: $white; + } +} + +/* +* BADGE_TITLE +* Display Title like a badge (with background-colored label) +*/ + +h2.badge-title { + display: flex; + flex-direction: row; + width: 100%; + color: $dark; + + a & { text-decoration: none; } // ?!? keep it ? + + span.title_label { + border-radius: 0.35rem 0 0 0.35rem; + color: $white; + font-size: 80%; + padding: 0.5em; + padding-right: 0; + h3 { + margin-bottom: 0.5rem; + } + } + span.title_action { + flex-grow: 1; + margin: 0 0 0 auto; + border-radius: 0 0.35rem 0.35rem 0; + background-color: $light; + padding: 0.2em 1em; + + ul.small_in_title { + margin-top: 0.5em; + font-size: 70%; + padding-left: 1rem; + } + } +} + +/// badge_title in AccompanyingCourse Work list Page +div.accompanying_course_work-list { + h2.badge-title { + span.title_label { + // Calculate same color then border:groove + background-color: shade-color($social-action-color, 34%); + } + span.title_action { + @include badge_title($social-action-color); + } + } +} + +/// badge_title in Activities on resume page +div.activity-list { + h2.badge-title { + span.title_label { + // Calculate same color then border:groove + background-color: shade-color($activity-color, 34%); + h3 { + color: $white; + } + } + span.title_action { + @include badge_title($activity-color); + } + span.title_label { + div.duration { + font-size: 70%; + font-weight: 500; + p { + margin-bottom: 0; + text-align: right; + abbr { + text-decoration: none; + } + } + } + } + } +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss index 4ff1d72bc..e995f97eb 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss @@ -1,44 +1,39 @@ -// Additionnal colors -$sky-blue: #4bafe8; - -// mixins variables -$social-issue-color: $sky-blue; -$social-action-color: $orange; - -// Calculate same color then border:groove -// origin: $orange, computed: #965028 -$social-action-label-color: shade-color($orange, 34%); - /// -/// Social Issue mixin -// define visual badge for all social issues +/// Chill badge mixin +// define chill visual badge /// -@mixin badge_social_issue { +@mixin chill_badge($color) { text-transform: capitalize !important; font-weight: 500 !important; - border-left: 20px groove $social-issue-color; + border-left: 20px groove $color; &:before { - content: '\f04b'; font-family: ForkAwesome; - color: $social-issue-color; + content: '\f04b'; + color: $color; + } +} + +/// +/// Social badge mixin +// define visual badge for social issues or social action +/// + +@mixin badge_social($color) { + @include chill_badge($color); + &:before { margin: 0 0.3em 0 -0.75em; } } /// -/// Social Action mixin -// define visual badge for all social actions +/// Generic mixin for titles like badge +// define visual badge used in title area /// -@mixin badge_social_action { - text-transform: capitalize !important; - font-weight: 500 !important; - border-left: 20px groove $social-action-color; +@mixin badge_title($color) { + @include chill_badge($color); &:before { - content: '\f04b'; - font-family: ForkAwesome; - color: $social-action-color; - margin: 0 0.3em 0 -0.75em; + margin: 0 0.3em 0 -1.05em; } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/render_box.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/render_box.scss index 1aa1c214b..230640bbd 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/render_box.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/render_box.scss @@ -8,21 +8,28 @@ span.altname {} } - /// SOCIAL-ISSUE - &.entity-social-issue { - margin-right: 0.3em; - font-size: 120%; - span.badge { - @include badge_social_issue; - } - } - - /// SOCIAL-ACTION + /// SOCIAL-ISSUE AND SOCIAL-ACTION + &.entity-social-issue, &.entity-social-action { margin-right: 0.3em; font-size: 120%; span.badge { - @include badge_social_action; + // for too long badge in a narrow inside block + text-align: initial; + margin-bottom: 0.2em; + > span { + white-space: normal; + } + } + } + &.entity-social-issue { + span.badge { + @include badge_social($social-issue-color); + } + } + &.entity-social-action { + span.badge { + @include badge_social($social-action-color); } } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue index d2cfe5da0..57d185820 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue @@ -12,9 +12,9 @@ export default { diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig index 13eb99c14..612c3a46b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig @@ -71,7 +71,7 @@
    {% if accompanyingCourse.locationStatus == 'person' %} -
    {{ 'This course is located by'|trans }}
    +

    {{ 'This course is located by'|trans }}

    {{ accompanyingCourse.personLocation|chill_entity_render_string }}

    {% elseif accompanyingCourse.locationStatus == 'address' %}

    {{ 'This course has a temporarily location'|trans }}

    @@ -82,97 +82,9 @@ {% endif %}
    - - {# DISABLED -

    {{ 'Resume Accompanying Course'|trans }}

    - -
    -

    {{ 'Associated peoples'|trans }}

    -
    - {% for participation in accompanyingCourse.participations %} - {% if participation.enddate is null %} -
    - {{ participation.person|chill_entity_render_box({ - 'render': 'bloc', 'addLink': false, 'addInfo': true, 'addAltNames': false, - 'customButtons': { 'before': _self.button_person(participation.person) } - }) }} -
    - {% endif %} - {% endfor %} -
    -
    - -
    -

    {{ 'Accompanying course location'|trans }}

    - - {% if accompanyingCourse.locationStatus == 'person' %} -

    {{ 'This course is located by'|trans }} - {{ accompanyingCourse.personLocation|chill_entity_render_string }} -

    - {% elseif accompanyingCourse.locationStatus == 'address' %} -

    {{ 'This course has a temporarily location'|trans }}

    - {% endif %} - - {% if accompanyingCourse.locationStatus != 'none' %} -
    -
    - {{ accompanyingCourse.location|chill_entity_render_box }} -
    -
    - {% endif %} -
    - -
    -

    {{ 'Requestor'|trans }}

    - {% if accompanyingCourse.requestorPerson is not empty %} - {% set requestor = accompanyingCourse.requestorPerson %} - {% set info = true %} - {% elseif accompanyingCourse.requestor is not empty %} - {% set requestor = accompanyingCourse.requestorThirdParty %} - {% set info = false %} - {% endif %} - {% if accompanyingCourse.requestor == null %} -

    {{ 'Any requestor to this accompanying course'|trans }}

    - {% else %} -
    -
    - {{ requestor|chill_entity_render_box({ - 'render': 'bloc', 'addLink': false, 'addEntity': true, 'addInfo': info, 'addAltNames': false - }) }} -
    -
    - {% endif %} -
    - -
    -

    {{ 'Resources'|trans }}

    - {% if accompanyingCourse.resources|length == 0 %} -

    {{ 'Any resource for this accompanying course'|trans }}

    - {% else %} -
    - {% for r in accompanyingCourse.resources %} -
    - {% if r.person %} - {{ r.person|chill_entity_render_box({ - 'render': 'bloc', 'addLink': false, 'addEntity': true, 'addInfo': true, 'addAltNames': false - }) }} - {% endif %} - {% if r.thirdParty %} - {{ r.thirdParty|chill_entity_render_box({ - 'render': 'bloc', 'addLink': false, 'addEntity': true, 'addInfo': false - }) }} - {% endif %} -
    - {% endfor %} -
    - {% endif %} -
    - #} -
    - {% block contentActivity %} @@ -189,9 +101,9 @@ {% set accompanying_course_id = accompanyingCourse.id %} {% endif %} -

    {{ 'Activity list' |trans }}

    +

    {{ 'Last activities' |trans }}

    - {% include 'ChillActivityBundle:Activity:list.html.twig' with { 'context': 'accompanyingCourse', 'no_action': true } %} + {% include 'ChillActivityBundle:Activity:list_recent.html.twig' with { 'context': 'accompanyingCourse', 'no_action': true } %} {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig index d44b7eb3b..f0f0a051e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig @@ -10,7 +10,7 @@
    -

    +

    {{ 'accompanying_course_work.action'|trans }} {{ w.socialAction|chill_entity_render_string }}

    diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig new file mode 100644 index 000000000..61f48aa76 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig @@ -0,0 +1,32 @@ + {% if works|length == 0 %} +

    {{ 'accompanying_course_work.Any work'|trans }} + {# TODO link #} +

    + {% endif %} + +
    + {% for w in works | slice(0,5) %} + + +

    + {{ 'accompanying_course_work.action'|trans }} + {{ w.socialAction|chill_entity_render_string }} + +
      +
    • + {{ 'accompanying_course_work.start_date'|trans ~ ' : ' }} + {{ w.startDate|format_date('short') }} +
    • +
    • + {{ 'Last updated by'|trans ~ ' : ' }} + {{ w.updatedBy|chill_entity_render_box }}, {{ w.updatedAt|format_datetime('short', 'short') }} +
    • +
    + +
    +

    + {% endfor %} +
    + + diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list.html.twig index bcb630cde..a53a7c0d0 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list.html.twig @@ -58,11 +58,10 @@
    -
    {% if accompanying_period.requestorPerson is not null or accompanying_period.requestorThirdParty is not null %}
    -

    {{ 'Requestor'|trans }}

    +

    {{ 'Requestor'|trans({'gender': null }) }}

    {% if accompanying_period.requestorPerson is not null %} {{ accompanying_period.requestorPerson|chill_entity_render_string }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/create.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/create.html.twig index bd1042dd7..799d71e19 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/create.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/create.html.twig @@ -61,7 +61,7 @@
    {% endif %} - {{ form_start(form) }} + {{ form_start(form, {'attr' : {'id' : 'create-form'}}) }} {{ form_row(form.lastName, { 'label' : 'Last name'|trans }) }} @@ -104,3 +104,7 @@
    {% endblock content %} + +{% block js %} + {{ encore_entry_script_tags('mod_disablebuttons') }} +{% endblock js %} diff --git a/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php b/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php new file mode 100644 index 000000000..c8690a237 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php @@ -0,0 +1,69 @@ +setFirstName($firstname); + $person->setLastName($lastname); + + $args = $this->createMock(LifecycleEventArgs::class); + $args->method('getObject') + ->willReturn($person); + + $listener->onPrePersist($args); + + $this->assertEquals($firstnameExpected, $person->getFirstName()); + $this->assertEquals($lastnameExpected, $person->getLastName()); + } + + /** + * @dataProvider generateAltNames + */ + public function testAltNamesOnPrePersist($altname, $altnameExpected) + { + $listener = new PersonEventListener(); + + $personAltname = new PersonAltName(); + + $personAltname->setLabel($altname); + + $args = $this->createMock(LifecycleEventArgs::class); + $args->method('getObject') + ->willReturn($personAltname); + + $listener->onPrePersist($args); + + $this->assertEquals($altnameExpected, $personAltname->getLabel()); + } + + public function generateNames(): iterator + { + yield ['émelie-marie', 'Émelie-Marie', 'lenaerts', 'LENAERTS']; + yield ['jean-marie', 'Jean-Marie', 'lenaerts', 'LENAERTS']; + yield ['vinCENT', 'Vincent', 'fastré', 'FASTRÉ']; + yield ['Vincent', 'Vincent', 'van Gogh', 'VAN GOGH']; + yield ['André marie', 'André Marie', 'Bah', 'BAH']; + } + + public function generateAltNames(): iterator + { + yield ['vinCENT', 'VINCENT']; + yield ['jean-marie', 'JEAN-MARIE']; + yield ['fastré', 'FASTRÉ']; + yield ['émile', 'ÉMILE']; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/config/services/doctrineEventListener.yaml b/src/Bundle/ChillPersonBundle/config/services/doctrineEventListener.yaml new file mode 100644 index 000000000..6d038d7a5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/config/services/doctrineEventListener.yaml @@ -0,0 +1,12 @@ +services: + Chill\PersonBundle\EventListener\PersonEventListener: + autoconfigure: true + tags: + - + name: 'doctrine.orm.entity_listener' + event: 'onPrePersist' + entity: 'Chill\PersonBundle\Entity\Person' + - + name: 'doctrine.orm.entity_listener' + event: 'onPrePersist' + entity: 'Chill\PersonBundle\Entity\PersonAltName' \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 0b6cb8e8f..a17bbc55f 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -191,6 +191,7 @@ Associated peoples: Usagers concernés Resources: Interlocuteurs privilégiés Any requestor to this accompanying course: Aucun demandeur pour ce parcours Social actions: Actions d'accompagnement +Last social actions: Les dernières actions d'accompagnement Social issues: Problématiques sociales Last events on accompanying course: Dernières actions de suivi Edit & activate accompanying course: Modifier et valider @@ -380,7 +381,7 @@ Create Accompanying Course: Créer un nouveau parcours Drop Accompanying Course: Supprimer le parcours This course is located at a temporarily address. You should locate this course to an user: Le parcours est localisé à une adresse temporaire. Il devrait être localisé auprès d'une personne concernée. Accompanying course location: Localisation du parcours -This course is located by: Parcours localisé auprès de +This course is located by: Localisé auprès de This course has a temporarily location: Localisation temporaire Choose a person to locate by: Localiser auprès d'un usager concerné Associate at least one member with an household, and set an address to this household: Associez au moins un membre du parcours à un ménage, et indiquez une adresse à ce ménage. diff --git a/src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php b/src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php index 632961953..0330699ad 100644 --- a/src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php +++ b/src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php @@ -115,16 +115,16 @@ class UserMenuBuilder implements LocalMenuBuilderInterface 'icon' => 'tasks' ]); - $menu->addChild("My calendar list", [ - 'route' => 'chill_calendar_calendar_list', - 'routeParameters' => [ - 'user_id' => $user->getId(), - ] - ]) - ->setExtras([ - 'order' => -9, - 'icon' => 'tasks' - ]); + // $menu->addChild("My calendar list", [ + // 'route' => 'chill_calendar_calendar_list', + // 'routeParameters' => [ + // 'user_id' => $user->getId(), + // ] + // ]) + // ->setExtras([ + // 'order' => -9, + // 'icon' => 'tasks' + // ]); /* $menu->addChild("My aside activities", [ diff --git a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php index 91c80dab7..d6e4c0046 100644 --- a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php +++ b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php @@ -2,7 +2,18 @@ namespace Chill\ThirdPartyBundle\Controller; +use Chill\MainBundle\CRUD\Controller\AbstractCRUDController; +use Chill\MainBundle\CRUD\Controller\CRUDController; +use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepositoryInterface; +use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; +use http\Exception\RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Annotation\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\HttpFoundation\Request; @@ -16,12 +27,7 @@ use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\Pagination\PaginatorFactory; -/** - * Routes for operations on ThirdParties. - * - * @Route("/{_locale}/thirdparty/thirdparty") - */ -class ThirdPartyController extends Controller +final class ThirdPartyController extends CRUDController { /** * @@ -41,145 +47,86 @@ class ThirdPartyController extends Controller */ protected $paginatorFactory; + protected ThirdPartyACLAwareRepositoryInterface $thirdPartyACLAwareRepository; + + protected RequestStack $requestStack; + public function __construct( AuthorizationHelper $authorizationHelper, TranslatorInterface $translator, - PaginatorFactory $paginatorFactory + PaginatorFactory $paginatorFactory, + RequestStack $requestStack, + ThirdPartyACLAwareRepositoryInterface $thirdPartyACLAwareRepository ) { $this->authorizationHelper = $authorizationHelper; $this->translator = $translator; $this->paginatorFactory = $paginatorFactory; + $this->requestStack = $requestStack; + $this->thirdPartyACLAwareRepository = $thirdPartyACLAwareRepository; } - - /** - * @Route("/index", name="chill_3party_3party_index") - */ - public function indexAction() + protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int { - $this->denyAccessUnlessGranted(ThirdPartyVoter::SHOW); - $repository = $this->getDoctrine()->getManager() - ->getRepository(ThirdParty::class); - - $nbThirdParties = $repository->count([]); //$repository->countByMemberOfCenters($centers); - $pagination = $this->paginatorFactory->create($nbThirdParties); - - $thirdParties = $repository->findAll(); - - return $this->render('ChillThirdPartyBundle:ThirdParty:index.html.twig', array( - 'third_parties' => $thirdParties, - 'pagination' => $pagination - )); - } - - /** - * @Route("/new", name="chill_3party_3party_new") - */ - public function newAction(Request $request) - { - $this->denyAccessUnlessGranted(ThirdPartyVoter::CREATE); - - $centers = []; - - $thirdParty = new ThirdParty(); - $thirdParty->setCenters(new ArrayCollection($centers)); - - $form = $this->createForm(ThirdPartyType::class, $thirdParty, [ - 'usage' => 'create' - ]); - $form->add('submit', SubmitType::class); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->getDoctrine()->getManager(); - $em->persist($thirdParty); - $em->flush(); - - $this->addFlash('success', - $this->translator->trans("Third party created") - ); - - return $this->redirectToRoute('chill_3party_3party_show', [ - 'thirdparty_id' => $thirdParty->getId() - ]); - - } elseif ($form->isSubmitted()) { - $msg = $this->translator->trans('This form contains errors'); - $this->addFlash('error', $msg); + if (NULL === $filterOrder){ + throw new \LogicException('filterOrder should not be null'); } - return $this->render('@ChillThirdParty/ThirdParty/new.html.twig', [ - 'form' => $form->createView(), - 'thirdParty' => $thirdParty - ]); + return $this->thirdPartyACLAwareRepository->countThirdParties(ThirdPartyVoter::SHOW, + $filterOrder->getQueryString()); } - /** - * @Route("/{thirdparty_id}/update", name="chill_3party_3party_update") - * @ParamConverter("thirdParty", options={"id": "thirdparty_id"}) - */ - public function updateAction(ThirdParty $thirdParty, Request $request) + protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null) { - $this->denyAccessUnlessGranted(ThirdPartyVoter::CREATE); + return $this->thirdPartyACLAwareRepository + ->listThirdParties(ThirdPartyVoter::SHOW, $filterOrder->getQueryString(), ['name' => 'ASC'], $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber()); + } - $repository = $this->getDoctrine()->getManager() - ->getRepository(ThirdParty::class); - - $centers = $repository->findAll(); - - // we want to keep centers the users has no access to. So we will add them - // later if they are removed. (this is a ugly hack but it will works - $centersAssociatedNotForUsers = \array_diff( - $thirdParty->getCenters()->toArray(), - $centers); - - $form = $this->createForm(ThirdPartyType::class, $thirdParty, [ - 'usage' => 'create' - ]); - $form->add('submit', SubmitType::class); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - // re-add centers the user has no accesses: - foreach ($centersAssociatedNotForUsers as $c) { - $thirdParty->addCenter($c); + protected function onPostCheckACL($action, Request $request, $entity): ?Response + { + if ('edit' === $action || 'view' === $action) { + if ($entity->isChild()) { + throw $this->createAccessDeniedException(); } - - $em = $this->getDoctrine()->getManager(); - $em->flush(); - - $this->addFlash('success', - $this->translator->trans("Third party updated") - ); - - return $this->redirectToRoute('chill_3party_3party_show', [ - 'thirdparty_id' => $thirdParty->getId() - ]); - - } elseif ($form->isSubmitted()) { - $msg = $this->translator->trans('This form contains errors'); - $this->addFlash('error', $msg); } - return $this->render('@ChillThirdParty/ThirdParty/update.html.twig', [ - 'form' => $form->createView(), - 'thirdParty' => $thirdParty - ]); + if ('new' === $action) { + if (!$request->query->has('kind')) { + return $this->render('@ChillThirdParty/ThirdParty/new_pick_kind.html.twig'); + } else { + $kind = $request->query->getAlpha('kind', ''); + + if (!(ThirdParty::KIND_COMPANY === $kind || ThirdParty::KIND_CONTACT === $kind)) { + throw new BadRequestHttpException('This kind is not supported: '.$kind); + } + + $entity->setKind($kind); + } + } + + return null; } - /** - * @Route("/{thirdparty_id}/show", name="chill_3party_3party_show") - * @ParamConverter("thirdParty", options={"id": "thirdparty_id"}) - */ - public function showAction(ThirdParty $thirdParty, Request $request) + protected function createFormFor(string $action, $entity, string $formClass = null, array $formOptions = []): FormInterface { - $this->denyAccessUnlessGranted(ThirdPartyVoter::SHOW, $thirdParty); + if ('new' === $action) { + return parent::createFormFor($action, $entity, $formClass, \array_merge( + $formOptions, [ 'kind' => $this->requestStack->getCurrentRequest()->query->getAlpha('kind')] + )); + } elseif ('edit' === $action) { + return parent::createFormFor($action, $entity, $formClass, \array_merge( + $formOptions, [ 'kind' => $entity->getKind()] + )); + } - return $this->render('@ChillThirdParty/ThirdParty/show.html.twig', [ - 'thirdParty' => $thirdParty - ]); + return parent::createFormFor($action, $entity, $formClass, $formOptions); } + protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper + { + return $this->getFilterOrderHelperFactory() + ->create(self::class) + ->addSearchBox(['name', 'company_name', 'acronym']) + ->build(); + } } diff --git a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php b/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php index 7fc5ea1c8..1d343f1fd 100644 --- a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php @@ -21,13 +21,14 @@ class LoadThirdParty extends Fixture Implements DependentFixtureInterface $thirdParties = $this->getThirdParties()->getObjects(); foreach ($thirdParties as $name => $thirdParty) { - if ('a' === $name[0]) { + if ('a' === $name[0]) { // this is an address continue; } + $thirdParty->setCreatedAt(new \DateTimeImmutable('today')); foreach ($this->getCenters() as $center) { - $thirdParty->addCenter($center); + $thirdParty->addCenter($center); } $manager->persist($thirdParty); @@ -38,7 +39,7 @@ class LoadThirdParty extends Fixture Implements DependentFixtureInterface private function getCenters(): \Iterator { - $references = \array_map(function($a) { return $a['ref']; }, + $references = \array_map(function($a) { return $a['ref']; }, LoadCenters::$centers); $number = random_int(1, count($references)); diff --git a/src/Bundle/ChillThirdPartyBundle/DependencyInjection/ChillThirdPartyExtension.php b/src/Bundle/ChillThirdPartyBundle/DependencyInjection/ChillThirdPartyExtension.php index 5ebac821b..bdf21c05a 100644 --- a/src/Bundle/ChillThirdPartyBundle/DependencyInjection/ChillThirdPartyExtension.php +++ b/src/Bundle/ChillThirdPartyBundle/DependencyInjection/ChillThirdPartyExtension.php @@ -2,6 +2,9 @@ namespace Chill\ThirdPartyBundle\DependencyInjection; +use Chill\ThirdPartyBundle\Controller\ThirdPartyController; +use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\ThirdPartyBundle\Form\ThirdPartyType; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\Extension; @@ -37,6 +40,7 @@ class ChillThirdPartyExtension extends Extension implements PrependExtensionInte $loader->load('services/menu.yaml'); $loader->load('services/fixtures.yaml'); $loader->load('services/serializer.yaml'); + $loader->load('services/repository.yaml'); } public function prepend(ContainerBuilder $container) @@ -54,6 +58,34 @@ class ChillThirdPartyExtension extends Extension implements PrependExtensionInte '@ChillThirdPartyBundle/config/routes.yaml' ) ], + 'cruds' => [ + [ + 'class' => ThirdParty::class, + 'controller' => ThirdPartyController::class, + 'name' => '3party_3party', + 'base_path' => '/3party/3party', + 'form_class' => ThirdPartyType::class, + 'actions' => [ + 'index' => [ + 'template' => '@ChillThirdParty/ThirdParty/index.html.twig', + 'role' => ThirdPartyVoter::SHOW, + ], + 'new' => [ + 'template' => '@ChillThirdParty/ThirdParty/new.html.twig', + 'role' => ThirdPartyVoter::CREATE, + ], + 'edit' => [ + 'template' => '@ChillThirdParty/ThirdParty/update.html.twig', + 'role' => ThirdPartyVoter::UPDATE, + ], + 'view' => [ + 'template' => '@ChillThirdParty/ThirdParty/view.html.twig', + 'role' => ThirdPartyVoter::SHOW, + ] + ] + + ] + ], 'apis' => [ [ 'class' => \Chill\ThirdPartyBundle\Entity\ThirdParty::class, diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 21d74f17e..b309c4542 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -22,6 +22,9 @@ namespace Chill\ThirdPartyBundle\Entity; +use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; +use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; +use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Entity\User; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\Collection; @@ -31,6 +34,7 @@ use Symfony\Component\Validator\Constraints as Assert; use Chill\MainBundle\Entity\Address; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; +use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint; /** * ThirdParty is a party recorded in the database. @@ -39,14 +43,13 @@ use Symfony\Component\Serializer\Annotation\Groups; * all users with the right 'CHILL_3PARTY_3PARTY_SEE', 'CHILL_3PARTY_3 to see, select and edit parties for this * center. * + * @ORM\Entity * @ORM\Table(name="chill_3party.third_party") - * @ORM\Entity(repositoryClass="Chill\ThirdPartyBundle\Repository\ThirdPartyRepository") * @DiscriminatorMap(typeProperty="type", mapping={ * "thirdparty"=ThirdParty::class * }) - * @ORM\HasLifecycleCallbacks() */ -class ThirdParty +class ThirdParty implements TrackCreationInterface, TrackUpdateInterface { /** * @var int @@ -54,7 +57,17 @@ class ThirdParty * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ - private $id; + private ?int $id = null; + + const KIND_CONTACT = 'contact'; + const KIND_COMPANY = 'company'; + const KIND_CHILD = 'child'; + + /** + * @ORM\Column(name="kind", type="string", length="20", options={"default":""}) + * @Groups({"write"}) + */ + private ?string $kind = ""; /** * @var string @@ -62,7 +75,7 @@ class ThirdParty * @Assert\Length(min="2") * @Groups({"read", "write"}) */ - private $name; + private ?string $name = ""; /** * [fr] Raison sociale @@ -71,7 +84,16 @@ class ThirdParty * @Assert\Length(min="3") * @Groups({"read", "write"}) */ - private $nameCompany; + private ?string $nameCompany = ""; + + /** + * Canonicalized form composed of name, company name and acronym. + * + * This field is read-only, and is generated on database side. + * + * @ORM\Column(name="canonicalized", type="text", options={"default":""}) + */ + private ?string $canonicalized = ""; /** * [fr] Sigle @@ -80,7 +102,7 @@ class ThirdParty * @Assert\Length(min="2") * @Groups({"read", "write"}) */ - private $acronym; + private ?string $acronym = ""; /** * @var ThirdPartyCategory @@ -89,18 +111,20 @@ class ThirdParty * joinColumns={@ORM\JoinColumn(name="thirdparty_id", referencedColumnName="id")}, * inverseJoinColumns={@ORM\JoinColumn(name="category_id", referencedColumnName="id")}) */ - private $categories; + private Collection $categories; /** * @var array|null * @ORM\Column(name="types", type="json", nullable=true) - * @Assert\Count(min=1) */ private $types; /** * Contact Persons: One Institutional ThirdParty has Many Contact Persons - * @ORM\OneToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty", mappedBy="parent") + * @ORM\OneToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty", mappedBy="parent", + * cascade={"persist"}, orphanRemoval=true) + * @var ThirdParty[]|Collection + * @Assert\Valid(traverse=true) */ private Collection $children; @@ -108,23 +132,24 @@ class ThirdParty * Institutional ThirdParty: Many Contact Persons have One Institutional ThirdParty * @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty", inversedBy="children") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") + * @Groups({"read"}) */ - private ?ThirdParty $parent; + private ?ThirdParty $parent = null; /** - * @var ThirdPartyCivility - * @ORM\OneToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdPartyCivility") - * @ORM\JoinColumn(name="civility", referencedColumnName="id", nullable=true) + * @var Civility + * @ORM\ManyToOne(targetEntity=Civility::class) + * ORM\JoinColumn(name="civility", referencedColumnName="id", nullable=true) */ - private $civility; + private ?Civility $civility = null; /** * [fr] Qualité * @var ThirdPartyProfession - * @ORM\OneToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdPartyProfession") - * @ORM\JoinColumn(name="profession", referencedColumnName="id", nullable=true) + * @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdPartyProfession") + * ORM\JoinColumn(name="profession", referencedColumnName="id", nullable=true) */ - private $profession; + private ?ThirdPartyProfession $profession = null; /** * @var string|null @@ -132,9 +157,10 @@ class ThirdParty * @Assert\Regex("/^([\+{1}])([0-9\s*]{4,20})$/", * message="Invalid phone number: it should begin with the international prefix starting with ""+"", hold only digits and be smaller than 20 characters. Ex: +33123456789" * ) + * @PhonenumberConstraint(type="any") * @Groups({"read", "write"}) */ - private $telephone; + private ?string $telephone = null; /** * @var string|null @@ -142,7 +168,13 @@ class ThirdParty * @Assert\Email(checkMX=false) * @Groups({"read", "write"}) */ - private $email; + private ?string $email = null; + + /** + * @var bool + * @ORM\Column(name="contact_data_anonymous", type="boolean", options={"default":false}) + */ + private bool $contactDataAnonymous = false; /** * @var Address|null @@ -151,27 +183,24 @@ class ThirdParty * @ORM\JoinColumn(nullable=true, onDelete="SET NULL") * @Groups({"read", "write"}) */ - private $address; + private ?Address $address = null; /** * Soft-delete flag - * @var boolean * @ORM\Column(name="active", type="boolean", options={"defaut": true}) */ - private $active = true; + private bool $active = true; /** - * @var string|null * @ORM\Column(name="comment", type="text", nullable=true) */ - private $comment; + private ?string $comment = null; /** - * @var Collection * @ORM\ManyToMany(targetEntity="\Chill\MainBundle\Entity\Center") * @ORM\JoinTable(name="chill_3party.party_center") */ - private $centers; + private Collection $centers; /** * @ORM\Column(name="created_at", type="datetime_immutable", nullable=false) @@ -179,33 +208,21 @@ class ThirdParty private \DateTimeImmutable $createdAt; /** - * @ORM\Column(name="updated_at", type="datetime", nullable=true) + * @ORM\Column(name="updated_at", type="datetime_immutable", nullable=true) */ - private ?\DateTime $updatedAt; + private ?\DateTimeImmutable $updatedAt; /** - * @var User * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") * @ORM\JoinColumn(name="updated_by", referencedColumnName="id") */ - private $updatedBy; - + private ?User $updatedBy; /** - * @ORM\PrePersist() + * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") + * @ORM\JoinColumn(name="created_by", referencedColumnName="id") */ - public function prePersist() - { - $this->createdAt = new \DateTimeImmutable(); - } - - /** - * @ORM\PreUpdate() - */ - public function preUpdate() - { - $this->updatedAt = new \DateTime(); - } + private ?User $createdBy; /** @@ -228,6 +245,17 @@ class ThirdParty return $this->id; } + public function getKind(): ?string + { + return $this->kind; + } + + public function setKind(?string $kind): ThirdParty + { + $this->kind = $kind; + return $this; + } + /** * Set name. * @@ -297,6 +325,17 @@ class ThirdParty return $this->email; } + public function isContactDataAnonymous(): bool + { + return $this->contactDataAnonymous; + } + + public function setContactDataAnonymous(bool $contactDataAnonymous): ThirdParty + { + $this->contactDataAnonymous = $contactDataAnonymous; + return $this; + } + /** * Set comment. * @@ -331,6 +370,10 @@ class ThirdParty // remove all keys from the input data $this->type = \array_values($type); + foreach ($this->children as $child) { + $child->setTypes($type); + } + return $this; } @@ -367,6 +410,9 @@ class ThirdParty public function setActive(bool $active) { $this->active = $active; + foreach ($this->children as $child) { + $child->setActive($active); + } return $this; } @@ -418,10 +464,10 @@ class ThirdParty } /** - * @param Address $address + * @param Address|null $address * @return $this */ - public function setAddress(Address $address) + public function setAddress(?Address $address = null) { $this->address = $address; @@ -448,9 +494,9 @@ class ThirdParty * @param string $nameCompany * @return ThirdParty */ - public function setNameCompany(string $nameCompany): ThirdParty + public function setNameCompany(?string $nameCompany): ThirdParty { - $this->nameCompany = $nameCompany; + $this->nameCompany = (string) $nameCompany; return $this; } @@ -486,7 +532,14 @@ class ThirdParty */ public function addCategory(ThirdPartyCategory $category): self { - $this->categories[] = $category; + if (!$this->categories->contains($category)) { + $this->categories[] = $category; + } + + foreach ($this->children as $child) { + $child->addCategory($child); + } + return $this; } @@ -497,6 +550,11 @@ class ThirdParty public function removeCategory(ThirdPartyCategory $category): self { $this->categories->removeElement($category); + + foreach ($this->children as $child) { + $child->removeCategory($child); + } + return $this; } @@ -509,19 +567,18 @@ class ThirdParty } /** - * isLeaf aliases + * @Groups({"read"}) */ public function isChild():bool { - return $this->isLeaf(); + return $this->parent !== null; } public function isParent():bool { - return !$this->isLeaf(); + return !$this->isChild(); } - /** * @return Collection */ @@ -530,6 +587,47 @@ class ThirdParty return $this->children; } + /** + * Get the children where active = true + * + * @return Collection + */ + public function getActiveChildren(): Collection + { + return $this->children->filter(fn (ThirdParty $tp) => $tp->getActive()); + } + + /** + * Add a child and set the child as active + * + * Method used in conjonction with getActiveChildren in form. + * + * @internal use the method addChild + * @param ThirdParty $child + * @return $this + */ + public function addActiveChild(ThirdParty $child): self + { + $child->setActive(true); + + return $this->addChild($child); + } + + /** + * mark the child as unactive, but keep the child existing in the + * database. To effectively remove the child, use removeChild instead. + * + * @param ThirdParty $child + * @return $this + */ + public function removeActiveChild(ThirdParty $child): self + { + $child->setActive(false); + + return $this; + } + + /** * @param ThirdParty $child * @return $this @@ -537,17 +635,24 @@ class ThirdParty public function addChild(ThirdParty $child): self { $this->children[] = $child; + $child->setParent($this)->setKind(ThirdParty::KIND_CHILD);; + return $this; } /** + * Remove the child from the database. + * + * If you want to keep the child into the database + * but desactivate it, use removeActiveChildren instead. + * * @param ThirdParty $child * @return $this */ public function removeChild(ThirdParty $child): self { $this->children->removeElement($child); - $this->active = false; + return $this; } @@ -570,18 +675,18 @@ class ThirdParty } /** - * @return ThirdPartyCivility|null + * @return Civility|null */ - public function getCivility(): ?ThirdPartyCivility + public function getCivility(): ?Civility { return $this->civility; } /** - * @param ThirdPartyCivility $civility + * @param Civility $civility * @return $this */ - public function setCivility(ThirdPartyCivility $civility): ThirdParty + public function setCivility(Civility $civility): ThirdParty { $this->civility = $civility; return $this; @@ -617,7 +722,7 @@ class ThirdParty * @param \DateTimeImmutable $createdAt * @return $this */ - public function setCreatedAt(\DateTimeImmutable $createdAt): ThirdParty + public function setCreatedAt(\DateTimeInterface $createdAt): ThirdParty { $this->createdAt = $createdAt; return $this; @@ -626,16 +731,16 @@ class ThirdParty /** * @return \DateTime|null */ - public function getUpdatedAt(): ?\DateTime + public function getUpdatedAt(): ?\DateTimeImmutable { return $this->updatedAt; } /** - * @param \DateTime $updatedAt + * @param \DateTimeImmutable $updatedAt * @return $this */ - public function setUpdatedAt(\DateTime $updatedAt): ThirdParty + public function setUpdatedAt(\DateTimeInterface $updatedAt): ThirdParty { $this->updatedAt = $updatedAt; return $this; @@ -659,6 +764,12 @@ class ThirdParty return $this; } + public function setCreatedBy(User $user): TrackCreationInterface + { + $this->createdBy = $user; + + return $this; + } } diff --git a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php index 8eae938f6..057f848cb 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php @@ -3,23 +3,26 @@ namespace Chill\ThirdPartyBundle\Form; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Entity\Civility; +use Chill\MainBundle\Form\Type\ChillCollectionType; +use Chill\MainBundle\Form\Type\PickCenterType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory; -use Chill\ThirdPartyBundle\Entity\ThirdPartyCivility; use Chill\ThirdPartyBundle\Entity\ThirdPartyProfession; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectManager; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Chill\ThirdPartyBundle\Security\Voter\ThirdPartyVoter; use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Symfony\Component\Security\Core\Role\Role; use Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -58,58 +61,10 @@ class ThirdPartyType extends AbstractType */ public function buildForm(FormBuilderInterface $builder, array $options) { - $types = []; - foreach ($this->typesManager->getProviders() as $key => $provider) { - $types['chill_3party.key_label.'.$key] = $key; - } - if (count($types) === 1) { - $builder - ->add('types', HiddenType::class, [ - 'data' => array_values($types) - ]) - ->get('types') - ->addModelTransformer(new CallbackTransformer( - function (?array $typeArray): ?string { - if (null === $typeArray) { - return null; - } - return implode(',', $typeArray); - }, - function (?string $typeStr): ?array { - if (null === $typeStr) { - return null; - } - return explode(',', $typeStr); - } - )) - ; - } else { - $builder->add('types', ChoiceType::class, [ - 'choices' => $types, - 'expanded' => true, - 'multiple' => true, - 'label' => 'thirdparty.Type' - ]); - } - $builder ->add('name', TextType::class, [ 'required' => true ]) - ->add('categories', EntityType::class, [ - 'label' => 'thirdparty.Categories', - 'class' => ThirdPartyCategory::class, - 'choice_label' => function (ThirdPartyCategory $category): string { - return $this->translatableStringHelper->localize($category->getName()); - }, - 'query_builder' => function (EntityRepository $er): QueryBuilder { - return $er->createQueryBuilder('c') - ->where('c.active = true'); - }, - 'required' => true, - 'multiple' => true, - 'attr' => ['class' => 'select2'] - ]) ->add('telephone', TextType::class, [ 'label' => 'Phonenumber', 'required' => false @@ -117,23 +72,16 @@ class ThirdPartyType extends AbstractType ->add('email', EmailType::class, [ 'required' => false ]) - ->add('active', ChoiceType::class, [ - 'label' => 'thirdparty.Status', - 'choices' => [ - 'Active, shown to users' => true, - 'Inactive, not shown to users' => false - ], - 'expanded' => true, - 'multiple' => false - ]) ->add('comment', ChillTextareaType::class, [ 'required' => false ]) - ->add('centers', EntityType::class, [ - 'choices' => $this->getReachableCenters($options), - 'class' => \Chill\MainBundle\Entity\Center::class, - 'multiple' => true, - 'attr' => ['class' => 'select2'] + ->add('centers', PickCenterType::class, [ + 'role' => (\array_key_exists('data', $options) && $this->om->contains($options['data'])) ? + ThirdPartyVoter::UPDATE : ThirdPartyVoter::CREATE, + 'choice_options' => [ + 'multiple' => true, + 'attr' => ['class' => 'select2'] + ] ]) ; @@ -159,12 +107,12 @@ class ThirdPartyType extends AbstractType ; // Contact Person ThirdParty (child) - if ($options['data']->isChild()) { + if (ThirdParty::KIND_CONTACT === $options['kind'] || ThirdParty::KIND_CHILD === $options['kind']) { $builder ->add('civility', EntityType::class, [ 'label' => 'thirdparty.Civility', - 'class' => ThirdPartyCivility::class, - 'choice_label' => function (ThirdPartyCivility $civility): string { + 'class' => Civility::class, + 'choice_label' => function (Civility $civility): string { return $this->translatableStringHelper->localize($civility->getName()); }, 'query_builder' => function (EntityRepository $er): QueryBuilder { @@ -172,7 +120,7 @@ class ThirdPartyType extends AbstractType ->where('c.active = true'); }, 'placeholder' => 'thirdparty.choose civility', - 'required' => true + 'required' => false ]) ->add('profession', EntityType::class, [ 'label' => 'thirdparty.Profession', @@ -187,6 +135,10 @@ class ThirdPartyType extends AbstractType 'placeholder' => 'thirdparty.choose profession', 'required' => false ]) + ->add('contactDataAnonymous', CheckboxType::class, [ + 'required' => false, + 'label' => 'thirdparty.Contact data are confidential' + ]) ; // Institutional ThirdParty (parent) @@ -200,49 +152,99 @@ class ThirdPartyType extends AbstractType 'label' => 'thirdparty.Acronym', 'required' => false ]) + ->add('activeChildren', ChillCollectionType::class, [ + 'entry_type' => ThirdPartyType::class, + 'entry_options' => [ + 'is_child' => true, + 'block_name' => 'children', + 'kind' => ThirdParty::KIND_CHILD, + ], + 'block_name' => 'active_children', + 'allow_add' => true, + 'allow_delete' => true, + 'by_reference' => false, + 'button_add_label' => "Add a contact", + 'button_remove_label' => "Remove a contact", + 'empty_collection_explain' => "Any contact" + ]) ; } - } - /** - * - * @param array $options - * @return \Chill\MainBundle\Entity\Center[] - */ - protected function getReachableCenters(array $options) - { - switch($options['usage']) { - case 'create': $role = new Role(ThirdPartyVoter::CREATE); - break; - case 'update': $role = new Role(ThirdPartyVoter::UPDATE); - break; + if (ThirdParty::KIND_CHILD !== $options['kind']) { + + $builder + ->add('categories', EntityType::class, [ + 'label' => 'thirdparty.Categories', + 'class' => ThirdPartyCategory::class, + 'choice_label' => function (ThirdPartyCategory $category): string { + return $this->translatableStringHelper->localize($category->getName()); + }, + 'query_builder' => function (EntityRepository $er): QueryBuilder { + return $er->createQueryBuilder('c') + ->where('c.active = true'); + }, + 'required' => true, + 'multiple' => true, + 'attr' => ['class' => 'select2'] + ]) + ->add('active', ChoiceType::class, [ + 'label' => 'thirdparty.Status', + 'choices' => [ + 'Active, shown to users' => true, + 'Inactive, not shown to users' => false + ], + 'expanded' => true, + 'multiple' => false + ]); + + // add the types + $types = []; + foreach ($this->typesManager->getProviders() as $key => $provider) { + $types['chill_3party.key_label.'.$key] = $key; + } + if (count($types) === 1) { + $builder + ->add('types', HiddenType::class, [ + 'data' => array_values($types) + ]) + ->get('types') + ->addModelTransformer(new CallbackTransformer( + function (?array $typeArray): ?string { + if (null === $typeArray) { + return null; + } + return implode(',', $typeArray); + }, + function (?string $typeStr): ?array { + if (null === $typeStr) { + return null; + } + return explode(',', $typeStr); + } + )) + ; + } else { + $builder + ->add('types', ChoiceType::class, [ + 'choices' => $types, + 'expanded' => true, + 'multiple' => true, + 'label' => 'thirdparty.Type' + ]); + } } - - return $this->authorizationHelper->getReachableCenters( - $this->tokenStorage->getToken()->getUser(), $role); } + /** * {@inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'data_class' => 'Chill\ThirdPartyBundle\Entity\ThirdParty' + 'data_class' => ThirdParty::class, + 'is_child' => false, + 'kind' => null )); - - $resolver->setRequired('usage') - ->setAllowedValues('usage', ['create', 'update']) - ; } - - /** - * {@inheritdoc} - */ - public function getBlockPrefix() - { - return 'chill_thirdpartybundle_thirdparty'; - } - - } diff --git a/src/Bundle/ChillThirdPartyBundle/Menu/MenuBuilder.php b/src/Bundle/ChillThirdPartyBundle/Menu/MenuBuilder.php index 654c47e86..e52ba66e1 100644 --- a/src/Bundle/ChillThirdPartyBundle/Menu/MenuBuilder.php +++ b/src/Bundle/ChillThirdPartyBundle/Menu/MenuBuilder.php @@ -37,13 +37,13 @@ class MenuBuilder implements LocalMenuBuilderInterface * @var AuthorizationCheckerInterface */ protected $authorizationChecker; - + /** * * @var TranslatorInterface */ protected $translator; - + public function __construct( AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator @@ -52,15 +52,15 @@ class MenuBuilder implements LocalMenuBuilderInterface $this->translator = $translator; } - + public function buildMenu($menuId, MenuItem $menu, array $parameters) { if ($this->authorizationChecker->isGranted(ThirdPartyVoter::SHOW)) { $menu ->addChild( - $this->translator->trans('Third parties'), + $this->translator->trans('Third parties'), [ - 'route' => 'chill_3party_3party_index', + 'route' => 'chill_crud_3party_3party_index', ]) ->setExtras([ 'order' => 112 diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php index ff21d18c7..ab535e60e 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php +++ b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php @@ -2,22 +2,62 @@ namespace Chill\ThirdPartyBundle\Repository; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Security\Core\Security; -/** - * @Author Mathieu Jaumotte mathieu.jaumotte@champs-libres.coop - */ -class ThirdPartyACLAwareRepository implements ThirdPartyACLAwareRepositoryInterface +final class ThirdPartyACLAwareRepository implements ThirdPartyACLAwareRepositoryInterface { + private Security $security; + private AuthorizationHelper $authorizationHelper; + private ThirdPartyRepository $thirdPartyRepository; - public function findByThirdparty( - ThirdParty $thirdparty, - string $role, - ?array $orderBy = [], - int $limit = null, - int $offset = null + public function __construct(Security $security, AuthorizationHelper $authorizationHelper, ThirdPartyRepository $thirdPartyRepository) + { + $this->security = $security; + $this->authorizationHelper = $authorizationHelper; + $this->thirdPartyRepository = $thirdPartyRepository; + } + + public function listThirdParties( + string $role, + ?string $filterString, + ?array $orderBy = [], + ?int $limit = null, + ?int $offset = null ): array { + $qb = $this->buildQuery($filterString); - // TODO: Implement findByThirdparty() method. + foreach ($orderBy as $sort => $direction) { + $qb->addOrderBy('tp.'.$sort, $direction); + } + + $qb->setFirstResult($offset) + ->setMaxResults($limit); + + return $qb->getQuery()->getResult(); + } + + public function countThirdParties( + string $role, + ?string $filterString + ): int { + $qb = $this->buildQuery($filterString); + $qb->select('count(tp)'); + + return $qb->getQuery()->getSingleScalarResult(); + } + + public function buildQuery(?string $filterString = null): QueryBuilder + { + $qb = $this->thirdPartyRepository->createQueryBuilder('tp'); + + if (NULL !== $filterString) { + $qb->andWhere($qb->expr()->like('tp.canonicalized', 'LOWER(UNACCENT(:filterString))')) + ->setParameter('filterString', '%'.$filterString.'%'); + } + + return $qb; } } diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepositoryInterface.php b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepositoryInterface.php index 263a1312c..459ac896a 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepositoryInterface.php @@ -6,11 +6,20 @@ use Chill\ThirdPartyBundle\Entity\ThirdParty; interface ThirdPartyACLAwareRepositoryInterface { - public function findByThirdparty( - ThirdParty $thirdparty, + public function countThirdParties(string $role, ?string $filterString): int; + + /** + * @param string $role + * @param array|null $orderBy + * @param int|null $limit + * @param int|null $offset + * @return array|ThirdParty[] + */ + public function listThirdParties( string $role, + ?string $filterString, ?array $orderBy = [], - int $limit = null, - int $offset = null + ?int $limit = 0, + ?int $offset = 50 ): array; } diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php index 65845b744..f91501c59 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php +++ b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php @@ -2,23 +2,26 @@ namespace Chill\ThirdPartyBundle\Repository; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Query; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Doctrine\Persistence\ObjectRepository; -class ThirdPartyRepository extends ServiceEntityRepository +final class ThirdPartyRepository implements ObjectRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $em) { - parent::__construct($registry, ThirdParty::class); + $this->repository = $em->getRepository(ThirdParty::class); } /** * count amongst parties associated to $centers, with $terms parameters - * + * * @param array $centers * @param type $terms * @return int @@ -27,24 +30,24 @@ class ThirdPartyRepository extends ServiceEntityRepository { $qb = $this->buildQuery($centers, $terms); $qb->select('COUNT(tp)'); - + return $qb->getQuery()->getSingleScalarResult(); } - + /** * Search amongst parties associated to $centers, with $terms parameters - * + * * Different format for return: * - ['entity']: return the entity hydrated as objects - * - ['array', [ DQL ]: return objects hydrated as entity, with + * - ['array', [ DQL ]: return objects hydrated as entity, with * an array describing the fields as DQL. - * + * * supported terms: - * + * * - name or _default: containing the name (name LIKE %string%) * - is_active: is active = true / false * - types: an array of types - * + * * @param array $centers * @param int $firstResult * @param int $maxResults @@ -74,43 +77,43 @@ class ThirdPartyRepository extends ServiceEntityRepository break; default: throw new \DomainException("This return format is invalid"); - } + } $qb->setFirstResult($firstResult) ->setMaxResults($maxResults); - + return $qb->getQuery()->getResult(); } - + protected function createMemberOfCentersQuery($centers): QueryBuilder { $qb = $this->createQueryBuilder('tp'); - + $or = $qb->expr()->orX(); - + foreach ($centers as $center) { $or->add($qb->expr()->isMemberOf(':center_'.$center->getId(), 'tp.centers')); $qb->setParameter('center_'.$center->getId(), $center); } - + $qb->where($or); - + return $qb; } - + protected function buildQuery($centers, $terms): QueryBuilder { $qb = $this->createMemberOfCentersQuery($centers); $this->setNameCondition($qb, $terms); $this->setTypesCondition($qb, $terms); $this->setIsActiveCondition($qb, $terms); - + return $qb; } - + /** - * Add parameters to filter by containing $terms["name"] or + * Add parameters to filter by containing $terms["name"] or * $terms["_default"] - * + * * @param QueryBuilder $qb * @param array $terms */ @@ -125,7 +128,7 @@ class ThirdPartyRepository extends ServiceEntityRepository $qb->setParameter('name', '%'.$term.'%'); } } - + protected function setTypesCondition(QueryBuilder $qb, array $terms) { if (\array_key_exists('types', $terms)) { @@ -137,14 +140,55 @@ class ThirdPartyRepository extends ServiceEntityRepository $qb->andWhere($orx); } } - + protected function setIsActiveCondition(QueryBuilder $qb, array $terms) { if (\array_key_exists('is_active', $terms)) { $qb->andWhere( - $terms['is_active'] ? $qb->expr()->eq('tp.active', "'TRUE'") : + $terms['is_active'] ? $qb->expr()->eq('tp.active', "'TRUE'") : $qb->expr()->eq('tp.active', "'FALSE'") ); } } + + public function find($id): ?ThirdParty + { + return $this->repository->find($id); + } + + /** + * @return array|ThirdParty[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @param array $criteria + * @param array|null $orderBy + * @param null $limit + * @param null $offset + * @return array|ThirdParty[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?ThirdParty + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return ThirdParty::class; + } + + public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder + { + return $this->repository->createQueryBuilder($alias, $indexBy); + } + } diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/chillthirdparty.scss b/src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/chillthirdparty.scss similarity index 100% rename from src/Bundle/ChillThirdPartyBundle/Resources/public/chill/chillthirdparty.scss rename to src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/chillthirdparty.scss diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/index.js b/src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/index.js similarity index 100% rename from src/Bundle/ChillThirdPartyBundle/Resources/public/chill/index.js rename to src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/index.js diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue index 21b79a1c7..401cf1fa8 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue @@ -20,6 +20,20 @@
    +
    +
    + + +
    +
    + + +
    +
    @@ -59,6 +73,17 @@ import ThirdPartyRenderBox from '../Entity/ThirdPartyRenderBox.vue'; import AddAddress from 'ChillMainAssets/vuejs/Address/components/AddAddress'; import { getThirdparty } from '../../_api/OnTheFly'; +const i18n = { + messages: { + fr: { + tparty: { + contact: "Contact", + company: "Institution" + } + } + } +}; + export default { name: "OnTheFlyThirdParty", props: ['id', 'type', 'action'], @@ -66,11 +91,12 @@ export default { ThirdPartyRenderBox, AddAddress }, + i18n, data() { return { //context: {}, <-- thirdparty: { - type: 'thirdparty' + type: 'thirdparty', }, addAddress: { options: { @@ -88,6 +114,19 @@ export default { } }, computed: { + kind: { + get() { + // note: there are also default to 'institution' set in the "mounted" method + if (this.$data.thirdparty.kind !== undefined) { + return this.$data.thirdparty.kind; + } else { + return 'company'; + } + }, + set(v) { + this.$data.thirdparty.kind = v; + } + }, context() { let context = { target: { @@ -133,6 +172,8 @@ export default { mounted() { if (this.action !== 'create') { this.loadData(); + } else { + this.thirdparty.kind = 'company'; } }, } diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig index 7768d2913..f7dcf3bc5 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig @@ -20,7 +20,7 @@ #} {% macro raw(thirdparty, options) %} - {{ thirdparty.name }} + {{ thirdparty|chill_entity_render_string }} {% endmacro raw %} {% macro label(thirdparty, options) %} @@ -29,7 +29,7 @@
    {%- if options['addLink'] and is_granted('CHILL_3PARTY_3PARTY_SHOW', thirdparty) -%} - + {%- endif -%} {% if options['customArea']['beforeLabel'] is defined %} @@ -41,7 +41,7 @@ {% if options['customArea']['afterLabel'] is defined %} {{ options['customArea']['afterLabel'] }} {% endif %} - + {%- if options['addLink'] and is_granted('CHILL_3PARTY_3PARTY_SHOW', thirdparty) -%} {%- endif -%} @@ -81,6 +81,13 @@
    {{ _self.label(thirdparty, options) }} + {% if thirdparty.kind == 'company' %} + {{ 'thirdparty.company'|trans }} + {% elseif thirdparty.kind == 'child' %} + {{ 'thirdparty.Child'|trans }} + {% elseif thirdparty.kind == 'contact' %} + {{ 'thirdparty.contact'|trans }} + {% endif %}
      @@ -110,10 +117,10 @@ {% if options['customButtons']['replace'] is defined %} {{ options['customButtons']['replace'] }} - {% elseif is_granted('CHILL_3PARTY_3PARTY_SHOW', thirdparty) %} + {% elseif is_granted('CHILL_3PARTY_3PARTY_SHOW', thirdparty) and options['addLink'] %}
    • + href="{{ path('chill_crud_3party_3party_view', { id: thirdparty.isChild ? thirdparty.parent.id : thirdparty.id }) }}">
    • {% else %} {% endif %} @@ -124,4 +131,17 @@
    + {% if options['showContacts'] and thirdparty.activeChildren|length > 0 %} +
    + {{ 'thirdparty.Children'|trans }} : + {% for c in thirdparty.activeChildren %} + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'thirdparty', id: c.id }, + action: 'show', + displayBadge: true, + buttonText: c|chill_entity_render_string + } %} + {% endfor %} +
    + {% endif %} {%- endif -%} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig new file mode 100644 index 000000000..ef347eb6d --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig @@ -0,0 +1,63 @@ + +{% if form.civility is defined %} + {{ form_row(form.civility) }} +{% endif %} + +{{ form_row(form.name) }} + +{% if form.nameCompany is defined %} + {{ form_row(form.nameCompany) }} + {{ form_row(form.acronym) }} +{% endif %} + +{% if form.profession is defined %} + {{ form_row(form.profession) }} +{% endif %} + +{{ form_row(form.types) }} +{{ form_row(form.categories) }} + +{{ form_row(form.telephone) }} +{{ form_row(form.email) }} + +{% if form.contactDataAnonymous is defined %} +{{ form_row(form.contactDataAnonymous) }} +{% endif %} + +{% if form.activeChildren is defined %} +

    {{ 'Contacts'|trans }}

    + {{ form_widget(form.activeChildren) }} +{% endif %} + +
    + {{ form_label(form.address) }} + {{ form_widget(form.address) }} +
    + {% if thirdParty.address %} + {# include vue_address component #} + {% include '@ChillMain/Address/_insert_vue_address.html.twig' with { + targetEntity: { name: 'thirdparty', id: thirdParty.id }, + mode: 'edit', + addressId: thirdParty.address.id, + buttonSize: 'btn-sm', + } %} + {# + backUrl: path('chill_3party_3party_new'), + #} + {% else %} + {# include vue_address component #} + {% include '@ChillMain/Address/_insert_vue_address.html.twig' with { + targetEntity: { name: 'thirdparty', id: thirdParty.id }, + mode: 'new', + buttonSize: 'btn-sm', + buttonText: 'Create a new address', + modalTitle: 'Create a new address', + } %} + {% endif %} +
    +
    + +{{ form_row(form.comment) }} +{{ form_row(form.centers) }} + +{{ form_row(form.active) }} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig new file mode 100644 index 000000000..eac5c2a98 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig @@ -0,0 +1,46 @@ + +{% block _third_party_active_children_entry_widget %} +
    +
    +
    + {{ form_widget(form.civility) }} + {{ form_errors(form.civility) }} + {{ form_label(form.civility) }} +
    +
    + {{ form_widget(form.name) }} + {{ form_errors(form.name) }} + {{ form_label(form.name) }} +
    +
    + {{ form_widget(form.profession) }} + {{ form_errors(form.profession) }} + {{ form_label(form.profession) }} +
    +
    +
    +
    + {{ form_widget(form.telephone) }} + {{ form_errors(form.telephone) }} + {{ form_label(form.telephone) }} +
    +
    + {{ form_widget(form.email) }} + {{ form_errors(form.email) }} + {{ form_label(form.email) }} +
    +
    + {{ form_widget(form.contactDataAnonymous) }} + {{ form_label(form.contactDataAnonymous) }} + {{ form_errors(form.contactDataAnonymous) }} +
    +
    +
    +
    + {{ form_widget(form.comment) }} + {{ form_errors(form.comment) }} +
    +
    +
    +{% endblock %} + diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/index.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/index.html.twig index 1cd1a6257..bada91f4c 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/index.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/index.html.twig @@ -2,108 +2,59 @@ {% block title 'List of third parties'|trans %} +{% set third_parties = entities %} + {% block content %} -
    -
    -
    + {% embed '@ChillMain/CRUD/_index.html.twig' %} + {% block index_header %} +

    {{ 'List of third parties'|trans }}

    + {% endblock %} -

    {{ 'List of third parties'|trans }}

    + {% block table_entities %} +
    +
    +
    - {% if third_parties|length == 0 %} -

    {{ 'No third parties'|trans }}

    - {% else %} + - +
    + {% for tp in third_parties %} +
    + {{ tp|chill_entity_render_box({'render': 'bloc', 'addLink': false, 'showContacts': true }) }} +
    +
      + {% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', tp) %} +
    • + +
    • + {% endif %} + {% if is_granted('CHILL_3PARTY_3PARTY_SHOW', tp) %} +
    • + +
    • + {% endif %} +
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - {% for tp in third_parties %} - - - - {% set types = [] %} - {% for t in tp.types %} - {% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %} + + {% endfor %} - - - - - - {% endfor %} - -
    {{ 'Name'|trans }} - - {{ 'Category'|trans }} - - {{ 'Address'|trans }} - - {{ 'thirdparty.UpdatedAt.short'|trans }} - -
    {{ (tp.active ? '' : '')|raw }}{{ tp.name }}{{ types|join(', ') }} - {{ tp.address|chill_entity_render_box({'multiline': false, 'with_valid_from': false}) }} - - {% if tp.updatedAt != null %} - {{ tp.updatedAt|format_date('short') }} - {% else %} - {{ tp.createdAt|format_date('short') }} - {% endif %} - -
      - {% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', tp) %} -
    • - -
    • - {% endif %} - {% if is_granted('CHILL_3PARTY_3PARTY_SHOW', tp) %} -
    • - -
    • - {% endif %} -
    -
    - - {% if third_parties|length < pagination.getTotalItems %} - {{ chill_pagination(pagination, 'long') }} - {% endif %} - - {% endif %} - - - -
    -
    -
    +
    +
    +
    +
    + {% endblock %} + {% block actions_before %} +
  • + {{ chill_items_per_page(paginator) }} +
  • + {% endblock %} + {% endembed %} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('page_3party_3party_index') }} {% endblock %} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new.html.twig index 319083649..a93d2d30b 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new.html.twig @@ -1,87 +1,21 @@ {% extends "@ChillMain/layout.html.twig" %} +{% set thirdParty = entity %} +{% form_theme form '@ChillThirdParty/ThirdParty/_form_thirdparty_children.html.twig' %} + {% block title 'Create third party'|trans %} {% block content %}
    -
    - -

    {{ 'Create third party'|trans }}

    - - {{ form_start(form) }} - - {% if form.civility is defined %} - {{ form_row(form.civility) }} - {% endif %} - - {{ form_row(form.name) }} - - {% if form.nameCompany is defined %} - {{ form_row(form.nameCompany) }} - {{ form_row(form.acronym) }} - {% endif %} - - {% if form.profession is defined %} - {{ form_row(form.profession) }} - {% endif %} - - {{ form_row(form.types) }} - {{ form_row(form.categories) }} - - {{ form_row(form.telephone) }} - {{ form_row(form.email) }} - -
    - {{ form_label(form.address) }} - {{ form_widget(form.address) }} -
    - {% if thirdParty.address %} - {# include vue_address component #} - {% include '@ChillMain/Address/_insert_vue_address.html.twig' with { - targetEntity: { name: 'thirdparty', id: thirdParty.id }, - mode: 'edit', - addressId: thirdParty.address.id, - buttonSize: 'btn-sm', - } %} - {# - backUrl: path('chill_3party_3party_new'), - #} - {% else %} - {# include vue_address component #} - {% include '@ChillMain/Address/_insert_vue_address.html.twig' with { - targetEntity: { name: 'thirdparty', id: thirdParty.id }, - mode: 'new', - buttonSize: 'btn-sm', - buttonText: 'Create a new address', - modalTitle: 'Create a new address', - } %} - {% endif %} -
    -
    - - {{ form_row(form.comment) }} - {{ form_row(form.centers) }} - - {{ form_row(form.active) }} - - - - {{ form_end(form) }} - -
    + {% embed '@ChillMain/CRUD/_new_content.html.twig' %} + {% block crud_content_header %} +

    {{ 'Create third party'|trans }}

    + {% endblock %} + {% block crud_content_form_rows %} + {% include '@ChillThirdParty/ThirdParty/_form.html.twig' %} + {% endblock %} + {% endembed %}
    {% endblock %} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new_pick_kind.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new_pick_kind.html.twig new file mode 100644 index 000000000..3ede02bfb --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new_pick_kind.html.twig @@ -0,0 +1,37 @@ +{% extends "@ChillMain/layout.html.twig" %} + +{% block title 'thirdparty.Which kind of third party ?'|trans %} + +{% block content %} +
    +

    {{ block('title') }}

    + +
    +
    + +
    +

    {{ 'thirdparty.a_company_explanation'|trans }}

    +
    +
    +
    + +
    +

    {{ 'thirdparty.a_contact_explanation'|trans }}

    +
    +
    +
    +
    + +{% endblock %} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/show.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/show.html.twig deleted file mode 100644 index a8dc587d7..000000000 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/show.html.twig +++ /dev/null @@ -1,122 +0,0 @@ -{% extends "@ChillMain/layout.html.twig" %} - -{% set title_ = 'Show third party %name%'|trans({'%name%' : thirdParty.name }) %} - -{% block title title_ %} - -{% block content %} -
    -
    -
    - -

    - {{ title_ }} - - {{ (thirdParty.active ? 'Active' : 'Inactive')|trans }} - -

    - - -
    - -
    {{ 'Name'|trans }}
    -
    - {% if thirdParty.isLeaf == true %}{{ thirdParty.civility }}{% endif %} - {{ thirdParty.name }} -
    - - {% if thirdParty.isLeaf == false %} -
    {{ 'thirdparty.NameCompany'|trans }}
    -
    - {% if thirdParty.nameCompany == null %} - {{ 'No nameCompany given'|trans }} - {% else %} - {{ thirdParty.nameCompany }} - {% endif %} -
    - -
    {{ 'thirdparty.Acronym'|trans }}
    -
    - {% if thirdParty.acronym == null %} - {{ 'No acronym given'|trans }} - {% else %} - {{ thirdParty.acronym }} - {% endif %} -
    - {% endif %} - -
    {{ 'Type'|trans }}
    - {% set types = [] %} - {% for t in thirdParty.types %} - {% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %} - {% endfor %} -
    - {{ types|join(', ') }} -
    - -
    {{ 'Phonenumber'|trans }}
    -
    - {% if thirdParty.telephone == null %} - {{ 'No phone given'|trans }} - {% else %} - - {{ thirdParty.telephone|chill_print_or_message("thirdparty.No_phonenumber") }} - - {% endif %} -
    - -
    {{ 'email'|trans }}
    -
    - {% if thirdParty.email == null %} - {{ 'No email given'|trans }} - {% else %} - - {{ thirdParty.email|chill_print_or_message("thirdparty.No_email") }} - - {% endif %} -
    - -
    {{ 'Address'|trans }}
    -
    - {% if thirdParty.address == null %} - {{ 'No address given'|trans }} - {% else %} - {{ thirdParty.address|chill_entity_render_box({'with_valid_from': false, 'extended_infos': true }) }} - {% endif %} -
    - -
    {{ 'Comment'|trans }}
    -
    - {% if thirdParty.comment is not empty %} -
    - {{ thirdParty.comment|chill_markdown_to_html }} -
    - {% endif %} -
    - -
    {{ 'Centers'|trans }}
    -
    {{ 'The party is visible in those centers'|trans }} : {{ thirdParty.centers|join(', ') }}
    - -
    - - - -
    -
    -
    -{% endblock %} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/update.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/update.html.twig index 4433d934b..2cdc3d281 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/update.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/update.html.twig @@ -1,30 +1,48 @@ {% extends "@ChillMain/layout.html.twig" %} +{% set thirdParty = entity %} +{% form_theme form '@ChillThirdParty/ThirdParty/_form_thirdparty_children.html.twig' %} + {% block title 'Update third party %name%'|trans({ '%name%': thirdParty.name }) %} {% block content %} +
    +
    + {% embed '@ChillMain/CRUD/_edit_content.html.twig' %} + {% block crud_content_header %} +

    + {{ 'Update third party %name%'|trans({ '%name%': thirdParty.name }) }} + + {{ (thirdParty.active ? 'Active' : 'Inactive')|trans }} + +

    + {% endblock %} + {% block crud_content_form_rows %} +
    + {% if thirdParty.updatedAt != null %} + {{ 'thirdparty.UpdatedAt.short'|trans ~ thirdParty.updatedAt|format_date('short') }} + {% else %} + {{ 'thirdparty.CreatedAt.short'|trans ~ thirdParty.createdAt|format_date('short') }} + {% endif %} + {% if thirdParty.updatedBy != null %} + {{ 'thirdparty.UpdateBy.short'|trans ~ thirdParty.updatedBy.usernameCanonical }} + {% endif %} +
    + {% include '@ChillThirdParty/ThirdParty/_form.html.twig' %} + {% endblock %} + {% endembed %} +
    +
    +{% endblock %} + + +{% block content_not %}
    -

    - {{ 'Update third party %name%'|trans({ '%name%': thirdParty.name }) }} - - {{ (thirdParty.active ? 'Active' : 'Inactive')|trans }} - -

    -
    - {% if thirdParty.updatedAt != null %} - {{ 'thirdparty.UpdatedAt.short'|trans ~ thirdParty.updatedAt|format_date('short') }} - {% else %} - {{ 'thirdparty.CreatedAt.short'|trans ~ thirdParty.createdAt|format_date('short') }} - {% endif %} - {% if thirdParty.updatedBy != null %} - {{ 'thirdparty.UpdateBy.short'|trans ~ thirdParty.updatedBy.usernameCanonical }} - {% endif %} -
    {{ form_start(form) }} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig new file mode 100644 index 000000000..460468e98 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig @@ -0,0 +1,139 @@ +{% extends "@ChillMain/layout.html.twig" %} + +{% set thirdParty = entity %} +{% set title_ = 'Show third party %name%'|trans({'%name%' : thirdParty.name }) %} + +{% block title title_ %} + +{% block content %} +
    +
    + {% embed '@ChillMain/CRUD/_view_content.html.twig' %} + {% block crud_content_header %} +

    + {{ title_ }} + + {{ (thirdParty.active ? 'Active' : 'Inactive')|trans }} + +

    + {% endblock %} + + {% block crud_content_view_details %} +
    + +
    {{ 'Name'|trans }}
    +
    + {% if thirdParty.isLeaf == true %}{{ thirdParty.civility }}{% endif %} + {{ thirdParty.name }} +
    + + {% if thirdParty.kind == 'company' %} +
    {{ 'thirdparty.NameCompany'|trans }}
    +
    + {% if thirdParty.nameCompany == null %} + {{ 'No nameCompany given'|trans }} + {% else %} + {{ thirdParty.nameCompany }} + {% endif %} +
    + +
    {{ 'thirdparty.Acronym'|trans }}
    +
    + {% if thirdParty.acronym == null %} + {{ 'No acronym given'|trans }} + {% else %} + {{ thirdParty.acronym }} + {% endif %} +
    + {% endif %} + +
    {{ 'Type'|trans }}
    + {% set types = [] %} + {% for t in thirdParty.types %} + {% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %} + {% endfor %} +
    + {{ types|join(', ') }} +
    + +
    {{ 'Phonenumber'|trans }}
    +
    + {% if thirdParty.telephone == null %} + {{ 'No phone given'|trans }} + {% else %} + + {{ thirdParty.telephone|chill_print_or_message("thirdparty.No_phonenumber") }} + + {% endif %} +
    + +
    {{ 'email'|trans }}
    +
    + {% if thirdParty.email == null %} + {{ 'No email given'|trans }} + {% else %} + + {{ thirdParty.email|chill_print_or_message("thirdparty.No_email") }} + + {% endif %} +
    + +
    {{ 'Address'|trans }}
    +
    + {% if thirdParty.address == null %} + {{ 'No address given'|trans }} + {% else %} + {{ thirdParty.address|chill_entity_render_box({'with_valid_from': false, 'extended_infos': true }) }} + {% endif %} +
    + +
    {{ 'Comment'|trans }}
    +
    + {% if thirdParty.comment is not empty %} +
    + {{ thirdParty.comment|chill_markdown_to_html }} +
    + {% else %} + {{ 'Any comment'|trans }} + {% endif %} +
    + + {% if thirdParty.kind == 'company' %} +
    {{ 'Contacts'|trans }}
    +
    + {% if thirdParty.activeChildren|length == 0 %} +

    {{ 'Any contacts associated'|trans }}

    + {% else %} +
    + {% for tp in thirdParty.activeChildren %} +
    + {{ tp|chill_entity_render_box({'render': 'bloc', 'addLink': false}) }} +
    + {% endfor %} +
    + {% endif %} +
    + {% endif %} + +
    {{ 'Centers'|trans }}
    +
    + {% set centers = thirdParty|chill_resolve_center %} + {% if centers is iterable %} + {{ 'The party is visible in those centers'|trans }} : + {{ centers|join(', ') }} + {% elseif centers is null %} + {{ 'The party is not visible in any center'|trans }} + {% else %} + {{ 'The party is visible in those centers'|trans }} : {{ centers }} + {% endif %} +
    + +
    + {% endblock %} + {% block content_form_actions_delete %}{% endblock %} + {% block content_view_actions_duplicate_link %}{% endblock %} + {% endembed %} +
    +
    +{% endblock %} diff --git a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php index 29a341b1b..47d5cc6b9 100644 --- a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php +++ b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php @@ -20,9 +20,14 @@ class ThirdPartyApiSearch implements SearchApiInterface return (new SearchApiQuery) ->setSelectKey('tparty') ->setSelectJsonbMetadata("jsonb_build_object('id', tparty.id)") - ->setSelectPertinence("SIMILARITY(?, LOWER(UNACCENT(tparty.name)))", [ $pattern ]) + ->setSelectPertinence("GREATEST(". + "STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized),". + "(tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')::int". + ")", [ $pattern, $pattern ]) ->setFromClause('chill_3party.third_party AS tparty') - ->setWhereClause('SIMILARITY(LOWER(UNACCENT(?)), LOWER(UNACCENT(tparty.name))) > 0.20', [ $pattern ]) + ->setWhereClause("tparty.active IS TRUE ". + "AND (LOWER(UNACCENT(?)) <<% tparty.canonicalized OR ". + "tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')", [ $pattern, $pattern ]) ; } @@ -33,7 +38,7 @@ class ThirdPartyApiSearch implements SearchApiInterface public function prepare(array $metadatas): void { - + } public function supportsResult(string $key, array $metadatas): bool diff --git a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php index b8c3e2827..d821011e5 100644 --- a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php +++ b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php @@ -3,6 +3,7 @@ namespace Chill\ThirdPartyBundle\Serializer\Normalizer; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -12,17 +13,27 @@ class ThirdPartyNormalizer implements NormalizerInterface, NormalizerAwareInterf { use NormalizerAwareTrait; + private ThirdPartyRender $thirdPartyRender; + + public function __construct(ThirdPartyRender $thirdPartyRender) + { + $this->thirdPartyRender = $thirdPartyRender; + } + public function normalize($thirdParty, string $format = null, array $context = []) { /** @var $thirdParty ThirdParty */ $data['type'] = 'thirdparty'; - // TODO should be replaced by a "render entity" - $data['text'] = $thirdParty->getName(); + $data['text'] = $this->thirdPartyRender->renderString($thirdParty, []); $data['id'] = $thirdParty->getId(); + $data['kind'] = $thirdParty->getKind(); $data['address'] = $this->normalizer->normalize($thirdParty->getAddress(), $format, [ 'address_rendering' => 'short' ]); $data['phonenumber'] = $thirdParty->getTelephone(); $data['email'] = $thirdParty->getEmail(); + $data['isChild'] = $thirdParty->isChild(); + $data['parent'] = $this->normalizer->normalize($thirdParty->getParent(), $format, $context); + $data['civility'] = $this->normalizer->normalize($thirdParty->getCivility(), $format, $context); return $data; } diff --git a/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php b/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php index 80450f7bb..db53108d4 100644 --- a/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php +++ b/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php @@ -21,6 +21,7 @@ namespace Chill\ThirdPartyBundle\Templating\Entity; use Chill\MainBundle\Templating\Entity\AbstractChillEntityRender; +use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Symfony\Component\Templating\EngineInterface; @@ -32,10 +33,15 @@ class ThirdPartyRender extends AbstractChillEntityRender { protected EngineInterface $engine; + protected TranslatableStringHelper $translatableStringHelper; - public function __construct(EngineInterface $engine) + public function __construct( + EngineInterface $engine, + TranslatableStringHelper $translatableStringHelper + ) { $this->engine = $engine; + $this->translatableStringHelper = $translatableStringHelper; } /** @@ -55,6 +61,7 @@ class ThirdPartyRender extends AbstractChillEntityRender 'hLevel' => $options['hLevel'] ?? 3, 'customButtons' => $options['customButtons'] ?? [], 'customArea' => $options['customArea'] ?? [], + 'showContacts' => $options['showContacts'] ?? [], ]; return @@ -75,7 +82,18 @@ class ThirdPartyRender extends AbstractChillEntityRender */ public function renderString($entity, array $options): string { - return $entity->getName(); + if ($entity->getCivility() !== NULL) { + $civility = $this->translatableStringHelper + ->localize($entity->getCivility()->getAbbreviation()).' '; + } else { + $civility = ''; + } + if (!empty($entity->getAcronym())) { + $acronym = ' ('.$entity->getAcronym().')'; + } else { + $acronym = ''; + } + return $civility.$entity->getName().$acronym; } public function supports($entity, array $options): bool diff --git a/src/Bundle/ChillThirdPartyBundle/chill.webpack.config.js b/src/Bundle/ChillThirdPartyBundle/chill.webpack.config.js index a31f8c5fc..c508cbc0c 100644 --- a/src/Bundle/ChillThirdPartyBundle/chill.webpack.config.js +++ b/src/Bundle/ChillThirdPartyBundle/chill.webpack.config.js @@ -1,9 +1,12 @@ module.exports = function(encore, entries) { - entries.push(__dirname + '/Resources/public/chill/index.js'); - // Aliases are used when webpack is trying to resolve modules path encore.addAliases({ ChillThirdPartyAssets: __dirname + '/Resources/public' }); + + encore.addEntry( + 'page_3party_3party_index', + __dirname + '/Resources/public/page/index/index.js' + ); }; diff --git a/src/Bundle/ChillThirdPartyBundle/config/services.yaml b/src/Bundle/ChillThirdPartyBundle/config/services.yaml index 438ed3ff0..8f9420a67 100644 --- a/src/Bundle/ChillThirdPartyBundle/config/services.yaml +++ b/src/Bundle/ChillThirdPartyBundle/config/services.yaml @@ -6,8 +6,3 @@ services: tags: - { name: 'serializer.normalizer', priority: 64 } - Chill\ThirdPartyBundle\Repository\: - autowire: true - resource: '../Repository/' - tags: - - { name: 'doctrine.repository_service' } diff --git a/src/Bundle/ChillThirdPartyBundle/config/services/controller.yaml b/src/Bundle/ChillThirdPartyBundle/config/services/controller.yaml index 14742d406..f10b58314 100644 --- a/src/Bundle/ChillThirdPartyBundle/config/services/controller.yaml +++ b/src/Bundle/ChillThirdPartyBundle/config/services/controller.yaml @@ -1,7 +1,5 @@ services: - Chill\ThirdPartyBundle\Controller\ThirdPartyController: - arguments: - $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' - $translator: '@Symfony\Component\Translation\TranslatorInterface' - $paginatorFactory: '@Chill\MainBundle\Pagination\PaginatorFactory' - tags: ['controller.service_arguments'] \ No newline at end of file + Chill\ThirdPartyBundle\Controller\: + resource: './../Controller' + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillThirdPartyBundle/config/services/fixtures.yaml b/src/Bundle/ChillThirdPartyBundle/config/services/fixtures.yaml index 33583f1aa..40f663e6d 100644 --- a/src/Bundle/ChillThirdPartyBundle/config/services/fixtures.yaml +++ b/src/Bundle/ChillThirdPartyBundle/config/services/fixtures.yaml @@ -4,10 +4,6 @@ services: tags: - { 'name': doctrine.fixture.orm } - Chill\ThirdPartyBundle\DataFixtures\ORM\LoadThirdPartyCivility: - tags: - - { 'name': doctrine.fixture.orm } - Chill\ThirdPartyBundle\DataFixtures\ORM\LoadThirdPartyCategory: tags: - { 'name': doctrine.fixture.orm } diff --git a/src/Bundle/ChillThirdPartyBundle/config/services/repository.yaml b/src/Bundle/ChillThirdPartyBundle/config/services/repository.yaml new file mode 100644 index 000000000..9d04f86b2 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/config/services/repository.yaml @@ -0,0 +1,8 @@ +--- +services: + Chill\ThirdPartyBundle\Repository\: + autowire: true + autoconfigure: true + resource: '../Repository/' + + Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepositoryInterface: '@Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepository' diff --git a/src/Bundle/ChillThirdPartyBundle/config/services/templating.yaml b/src/Bundle/ChillThirdPartyBundle/config/services/templating.yaml index 6b12d3aa0..0e5a26df2 100644 --- a/src/Bundle/ChillThirdPartyBundle/config/services/templating.yaml +++ b/src/Bundle/ChillThirdPartyBundle/config/services/templating.yaml @@ -1,6 +1,6 @@ services: Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender: - arguments: - $engine: '@Symfony\Component\Templating\EngineInterface' + autowire: true + autoconfigure: true tags: - 'chill.render_entity' diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20211006200924.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211006200924.php new file mode 100644 index 000000000..d35552604 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211006200924.php @@ -0,0 +1,37 @@ +addSql('ALTER TABLE chill_3party.third_party ADD created_by INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER created_at TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER created_at DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER updated_at TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER updated_at DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_3party.third_party.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_3party.third_party.updated_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT FK_D952467BDE12AB56 FOREIGN KEY (created_by) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_D952467BDE12AB56 ON chill_3party.third_party (created_by)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_3party.third_party DROP created_by'); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007150459.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007150459.php new file mode 100644 index 000000000..fb4f9bed1 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007150459.php @@ -0,0 +1,69 @@ +addSql('DROP INDEX chill_3party.uniq_d952467b384d4799'); + $this->addSql('DROP INDEX chill_3party.uniq_d952467bba930d69'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD civility_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD profession_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD kind VARCHAR(20) NOT NULL DEFAULT \'\''); + $this->addSql('ALTER TABLE chill_3party.third_party ADD canonicalized TEXT NOT NULL DEFAULT \'\''); + $this->addSql('CREATE TEMPORARY TABLE civility_migration AS SELECT * FROM chill_3party.party_civility'); + $this->addSql('ALTER TABLE civility_migration ADD COLUMN new_id INT DEFAULT NULL'); + $this->addSql('UPDATE civility_migration SET new_id = nextval(\'chill_main_civility_id_seq\')'); + $this->addSql(' + INSERT INTO chill_main_civility (id, name, abbreviation, active) + SELECT new_id, name, \'{}\'::json, active from civility_migration + '); + $this->addSql('UPDATE chill_3party.third_party SET civility_id = new_id + FROM civility_migration WHERE civility_migration.id = third_party.civility'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT fk_d952467b384d4799'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT fk_d952467bba930d69'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP civility'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP profession'); + $this->addSql('DROP SEQUENCE chill_3party.party_civility_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_3party.party_civility'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT FK_D952467B23D6A298 FOREIGN KEY (civility_id) REFERENCES chill_main_civility (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT FK_D952467BFDEF8996 FOREIGN KEY (profession_id) REFERENCES chill_3party.party_profession (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_D952467B23D6A298 ON chill_3party.third_party (civility_id)'); + $this->addSql('CREATE INDEX IDX_D952467BFDEF8996 ON chill_3party.third_party (profession_id)'); + } + + public function down(Schema $schema): void + { + $this->throwIrreversibleMigrationException('Reversible migration not implemented'); + + // for reference: + $this->addSql('CREATE SEQUENCE chill_3party.party_civility_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_3party.party_civility (id INT NOT NULL, name JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT FK_D952467B23D6A298'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT FK_D952467BFDEF8996'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD civility INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD profession INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP civility_id'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP profession_id'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP kind'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP canonicalized'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT fk_d952467b384d4799 FOREIGN KEY (civility) REFERENCES chill_3party.party_civility (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT fk_d952467bba930d69 FOREIGN KEY (profession) REFERENCES chill_3party.party_profession (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE UNIQUE INDEX uniq_d952467b384d4799 ON chill_3party.third_party (civility)'); + $this->addSql('CREATE UNIQUE INDEX uniq_d952467bba930d69 ON chill_3party.third_party (profession)'); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007165001.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007165001.php new file mode 100644 index 000000000..f58fa1a79 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007165001.php @@ -0,0 +1,86 @@ +addSql(" + UPDATE chill_3party.third_party + SET canonicalized = + UNACCENT( + LOWER( + name || + CASE WHEN COALESCE(name_company, '') <> '' THEN ' ' ELSE '' END || + COALESCE(name_company, '') || + CASE WHEN COALESCE(acronym, '') <> '' THEN ' ' ELSE '' END || + COALESCE(acronym, '') + ) + ) + "); + $this->addSql(" + CREATE OR REPLACE FUNCTION chill_3party.canonicalize() RETURNS TRIGGER + LANGUAGE plpgsql + AS + $$ + BEGIN + NEW.canonicalized = + UNACCENT( + LOWER( + NEW.name || + CASE WHEN COALESCE(NEW.name_company, '') <> '' THEN ' ' ELSE '' END || + COALESCE(NEW.name_company, '') || + CASE WHEN COALESCE(NEW.acronym, '') <> '' THEN ' ' ELSE '' END || + COALESCE(NEW.acronym, '') + ) + ) + ; + + return NEW; + END + $$ + "); + $this->addSql(" + CREATE TRIGGER canonicalize_fullname_on_insert + BEFORE INSERT + ON chill_3party.third_party + FOR EACH ROW + EXECUTE procedure chill_3party.canonicalize(); + "); + $this->addSql(" + CREATE TRIGGER canonicalize_fullname_on_update + BEFORE UPDATE + ON chill_3party.third_party + FOR EACH ROW + EXECUTE procedure chill_3party.canonicalize(); + "); + $this->addSql(" + CREATE INDEX chill_custom_canonicalized_trgm_idx_gist + ON chill_3party.third_party USING GIST (canonicalized gist_trgm_ops) WHERE active IS TRUE + "); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TRIGGER canonicalize_fullname_on_update ON chill_3party.third_party'); + $this->addSql('DROP TRIGGER canonicalize_fullname_on_insert ON chill_3party.third_party'); + $this->addSql('DROP FUNCTION chill_3party.canonicalize()'); + $this->addSql(" + DROP INDEX chill_3party.chill_custom_canonicalized_trgm_idx_gist + "); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007194942.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007194942.php new file mode 100644 index 000000000..7a6f5fb76 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007194942.php @@ -0,0 +1,29 @@ +addSql('ALTER TABLE chill_3party.third_party ADD contact_data_anonymous BOOLEAN DEFAULT \'false\' NOT NULL;'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_3party.third_party DROP contact_data_anonymous'); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index 7878ec196..604eafbad 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -29,6 +29,17 @@ thirdparty.UpdateBy.short: ' par ' thirdparty.CreatedAt.long: Date de création thirdparty.UpdatedAt.long: Date de la dernière modification thirdparty.UpdateBy.long: Utilisateur qui a effectué la dernière modification +thirdparty.A company: Une institution +thirdparty.company: Institution +thirdparty.A contact: Une personne physique +thirdparty.contact: Personne physique +thirdparty.a_company_explanation: >- + Les institutions peuvent compter un ou plusieurs contacts, interne à l'instution. Il est également possible de + leur associer un acronyme, et le nom d'un service. +thirdparty.a_contact_explanation: >- + Les personnes physiques ne disposent pas d'acronyme, de service, ou de contacts sous-jacents. +thirdparty.Which kind of third party ?: Quel type de tiers souhaitez-vous créer ? +thirdparty.Contact data are confidential: Données de contact confidentielles New third party: Ajouter un nouveau tiers Show third party %name%: Tiers "%name%" @@ -45,6 +56,10 @@ Inactive, not shown to users: Inactif, invisible pour les utilisateurs Inactive: Inactif not shown to users: invisible pour les utilisateurs Show thirdparty: Voir le tiers +Add a contact: Ajouter un contact +Remove a contact: Supprimer +Contacts: Contacts +Any contact: Aucun contact No nameCompany given: Aucune raison sociale renseignée No acronym given: Aucun sigle renseigné @@ -52,9 +67,16 @@ No phone given: Aucun téléphone renseigné No email given: Aucune adresse courriel renseignée The party is visible in those centers: Le tiers est visible dans ces centres +The party is not visible in any center: Le tiers n'est associé à aucun centre No third parties: Aucun tiers # ROLES CHILL_3PARTY_3PARTY_CREATE: Ajouter un Tiers CHILL_3PARTY_3PARTY_SHOW: Voir un Tiers CHILL_3PARTY_3PARTY_UPDATE: Modifier un Tiers + +# crud: +crud: + 3party_3party: + index: + add_new: Créer