Compare commits

..

2 Commits

12 changed files with 72 additions and 56 deletions

View File

@@ -0,0 +1,6 @@
kind: UX
body: Remove the label if there is only one scope and no scope picking field is displayed.
time: 2025-10-30T15:31:26.807444365+01:00
custom:
Issue: "449"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: UX
body: Display whether doc generation template is active or not in admin and order templates alphabetically
time: 2025-11-03T16:19:10.051947925+01:00
custom:
Issue: "456"
SchemaChange: No schema change

View File

@@ -88,8 +88,8 @@ class ActivityType extends AbstractType
if (null !== $options['data']->getPerson()) { if (null !== $options['data']->getPerson()) {
$builder->add('scope', ScopePickerType::class, [ $builder->add('scope', ScopePickerType::class, [
'center' => $options['center'],
'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'], 'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'],
'center' => $options['center'],
'required' => true, 'required' => true,
]); ]);
} }

View File

@@ -25,24 +25,12 @@
<div class="item-bloc"> <div class="item-bloc">
<div class="item-row"> <div class="item-row">
<div class="item-col" style="flex-basis:100%;"> <div class="item-col" style="flex-basis:100%;">
<h2>{{ entity.name|localize_translatable_string }} </h2> <h2>{{ entity.name|localize_translatable_string }}</h2>
<p style="margin-left: 1rem;"><span class="badge bg-chill-gray">
{% if entity.active %}
{{ 'admin.active'|trans }}
{% else %}
{{ 'admin.not active'|trans }}
{% endif %}
</span></p>
</div> </div>
</div> </div>
<div class="item-row"> <div class="item-row">
<p><span class="badge bg-chill-green-dark">{{ contextManager.getContextByKey(entity.context).name|trans }}</span></p> <p><span class="badge bg-chill-green-dark">{{ contextManager.getContextByKey(entity.context).name|trans }}</span></p>
</div> </div>
{# <div class="item-row">#}
{# <div class="item-col" style="flex-basis:100%;">#}
{##}
{# </div>#}
{# </div>#}
<div class="item-row"> <div class="item-row">
<div class="item-col"></div> <div class="item-col"></div>
<ul class="record_actions item-col flex-shrink-1"> <ul class="record_actions item-col flex-shrink-1">

View File

@@ -49,7 +49,3 @@ crud:
Template file: Fichier modèle Template file: Fichier modèle
admin:
active: Actif
not active: Non-actif

View File

@@ -17,6 +17,7 @@ use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\ScopePickerType; use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
@@ -27,10 +28,11 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Security;
class PersonDocumentType extends AbstractType class PersonDocumentType extends AbstractType
{ {
public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly ScopeResolverDispatcher $scopeResolverDispatcher, private readonly ParameterBagInterface $parameterBag, private readonly CenterResolverDispatcher $centerResolverDispatcher) {} public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly Security $security, private readonly AuthorizationHelperInterface $authorizationHelper, private readonly ScopeResolverDispatcher $scopeResolverDispatcher, private readonly ParameterBagInterface $parameterBag, private readonly CenterResolverDispatcher $centerResolverDispatcher) {}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
@@ -57,8 +59,8 @@ class PersonDocumentType extends AbstractType
if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) { if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) {
$builder->add('scope', ScopePickerType::class, [ $builder->add('scope', ScopePickerType::class, [
'center' => $this->centerResolverDispatcher->resolveCenter($document),
'role' => $options['role'], 'role' => $options['role'],
'subject' => $document,
]); ]);
} }
} }

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper; use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
@@ -32,65 +33,84 @@ use Symfony\Component\Security\Core\Security;
* Allow to pick amongst available scope for the current * Allow to pick amongst available scope for the current
* user. * user.
* *
* options : * Options:
* * - `role`: string, the role to check permissions for
* - `center`: the center of the entity * - Either `subject`: object, entity to resolve centers from
* - `role` : the role of the user * - Or `center`: Center|array|null, the center(s) to check
*/ */
class ScopePickerType extends AbstractType class ScopePickerType extends AbstractType
{ {
public function __construct( public function __construct(
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly AuthorizationHelperInterface $authorizationHelper, private readonly AuthorizationHelperInterface $authorizationHelper,
private readonly Security $security, private readonly Security $security,
private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly CenterResolverManagerInterface $centerResolverManager,
) {} ) {}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$items = array_values( // Compute centers from subject
$centers = $options['center'] ?? null;
if (null === $centers && isset($options['subject'])) {
$centers = $this->centerResolverManager->resolveCenters($options['subject']);
}
if (null === $centers) {
throw new \RuntimeException('Either "center" or "subject" must be provided');
}
$reachableScopes = array_values(
array_filter( array_filter(
$this->authorizationHelper->getReachableScopes( $this->authorizationHelper->getReachableScopes(
$this->security->getUser(), $this->security->getUser(),
$options['role'], $options['role'],
$options['center'] $centers
), ),
static fn (Scope $s) => $s->isActive() static fn (Scope $s) => $s->isActive()
) )
); );
if (0 === \count($items)) { $builder->setAttribute('reachable_scopes_count', count($reachableScopes));
throw new \RuntimeException('no scopes are reachable. This form should not be shown to user');
if (0 === count($reachableScopes)) {
$builder->setAttribute('has_scopes', false);
return;
} }
if (1 !== \count($items)) { $builder->setAttribute('has_scopes', true);
if (1 !== count($reachableScopes)) {
$builder->add('scope', EntityType::class, [ $builder->add('scope', EntityType::class, [
'class' => Scope::class, 'class' => Scope::class,
'placeholder' => 'Choose the circle', 'placeholder' => 'Choose the circle',
'choice_label' => fn (Scope $c) => $this->translatableStringHelper->localize($c->getName()), 'choice_label' => fn (Scope $c) => $this->translatableStringHelper->localize($c->getName()),
'choices' => $items, 'choices' => $reachableScopes,
]); ]);
$builder->setDataMapper(new ScopePickerDataMapper()); $builder->setDataMapper(new ScopePickerDataMapper());
} else { } else {
$builder->add('scope', HiddenType::class, [ $builder->add('scope', HiddenType::class, [
'data' => $items[0]->getId(), 'data' => $reachableScopes[0]->getId(),
]); ]);
$builder->setDataMapper(new ScopePickerDataMapper($items[0])); $builder->setDataMapper(new ScopePickerDataMapper($reachableScopes[0]));
} }
} }
public function buildView(FormView $view, FormInterface $form, array $options) public function buildView(FormView $view, FormInterface $form, array $options)
{ {
$view->vars['fullWidth'] = true; $view->vars['fullWidth'] = true;
// display of label is handled by the EntityType
$view->vars['label'] = false;
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$resolver $resolver
// create `center` option
->setRequired('center')
->setAllowedTypes('center', [Center::class, 'array', 'null'])
// create ``role` option
->setRequired('role') ->setRequired('role')
->setAllowedTypes('role', ['string']); ->setAllowedTypes('role', ['string'])
->setDefined('subject')
->setAllowedTypes('subject', ['object'])
->setDefined('center')
->setAllowedTypes('center', [Center::class, 'array', 'null']);
} }
} }

View File

@@ -11,11 +11,11 @@ declare(strict_types=1);
namespace Form\Type; namespace Form\Type;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ScopePickerType; use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
@@ -39,11 +39,11 @@ final class ScopePickerTypeTest extends TypeTestCase
{ {
use ProphecyTrait; use ProphecyTrait;
public function estBuildOneScopeIsSuccessful() public function testBuildOneScopeIsSuccessful()
{ {
$form = $this->factory->create(ScopePickerType::class, null, [ $form = $this->factory->create(ScopePickerType::class, null, [
'center' => new Center(),
'role' => 'ONE_SCOPE', 'role' => 'ONE_SCOPE',
'center' => [],
]); ]);
$view = $form->createView(); $view = $form->createView();
@@ -54,8 +54,8 @@ final class ScopePickerTypeTest extends TypeTestCase
public function testBuildThreeScopesIsSuccessful() public function testBuildThreeScopesIsSuccessful()
{ {
$form = $this->factory->create(ScopePickerType::class, null, [ $form = $this->factory->create(ScopePickerType::class, null, [
'center' => new Center(),
'role' => 'THREE_SCOPE', 'role' => 'THREE_SCOPE',
'center' => [],
]); ]);
$view = $form->createView(); $view = $form->createView();
@@ -66,8 +66,8 @@ final class ScopePickerTypeTest extends TypeTestCase
public function testBuildTwoScopesIsSuccessful() public function testBuildTwoScopesIsSuccessful()
{ {
$form = $this->factory->create(ScopePickerType::class, null, [ $form = $this->factory->create(ScopePickerType::class, null, [
'center' => new Center(),
'role' => 'TWO_SCOPE', 'role' => 'TWO_SCOPE',
'center' => [],
]); ]);
$view = $form->createView(); $view = $form->createView();
@@ -101,10 +101,13 @@ final class ScopePickerTypeTest extends TypeTestCase
static fn ($args) => $args[0]['fr'] static fn ($args) => $args[0]['fr']
); );
$centerResolverManager = $this->prophesize(CenterResolverManagerInterface::class);
$type = new ScopePickerType( $type = new ScopePickerType(
$translatableStringHelper->reveal(),
$authorizationHelper->reveal(), $authorizationHelper->reveal(),
$security->reveal(), $security->reveal(),
$translatableStringHelper->reveal() $centerResolverManager->reveal()
); );
// add the mocks for creating EntityType // add the mocks for creating EntityType

View File

@@ -168,9 +168,8 @@ final readonly class PersonContext implements PersonContextInterface
if ($this->isScopeNecessary($entity)) { if ($this->isScopeNecessary($entity)) {
$builder->add('scope', ScopePickerType::class, [ $builder->add('scope', ScopePickerType::class, [
'center' => $this->centerResolverManager->resolveCenters($entity),
'role' => PersonDocumentVoter::CREATE, 'role' => PersonDocumentVoter::CREATE,
'label' => 'Scope', 'subject' => $entity,
]); ]);
} }
} }

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\DateIntervalType; use Chill\MainBundle\Form\Type\DateIntervalType;
use Chill\MainBundle\Form\Type\ScopePickerType; use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Form\Type\UserPickerType; use Chill\MainBundle\Form\Type\UserPickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\TaskBundle\Security\Authorization\TaskVoter; use Chill\TaskBundle\Security\Authorization\TaskVoter;
@@ -24,10 +25,17 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Security;
class SingleTaskType extends AbstractType class SingleTaskType extends AbstractType
{ {
public function __construct(private readonly ParameterBagInterface $parameterBag, private readonly CenterResolverDispatcherInterface $centerResolverDispatcher, private readonly ScopeResolverDispatcher $scopeResolverDispatcher) {} public function __construct(
private readonly ParameterBagInterface $parameterBag,
private readonly CenterResolverDispatcherInterface $centerResolverDispatcher,
private readonly Security $security,
private readonly AuthorizationHelperInterface $authorizationHelper,
private readonly ScopeResolverDispatcher $scopeResolverDispatcher,
) {}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
@@ -64,8 +72,8 @@ class SingleTaskType extends AbstractType
if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) { if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) {
$builder $builder
->add('circle', ScopePickerType::class, [ ->add('circle', ScopePickerType::class, [
'center' => $center,
'role' => $options['role'], 'role' => $options['role'],
'subject' => $task,
'required' => true, 'required' => true,
]); ]);
} }

View File

@@ -5,7 +5,7 @@
{% block title 'Tasks for {{ name }}'|trans({ '{{ name }}' : person|chill_entity_render_string }) %} {% block title 'Tasks for {{ name }}'|trans({ '{{ name }}' : person|chill_entity_render_string }) %}
{% block content %} {% block content %}
<div class="col-md-10 col-xxl"> <div class="task-list"">
<h1>{{ block('title') }}</h1> <h1>{{ block('title') }}</h1>

View File

@@ -37,7 +37,7 @@
{% endblock %} {% endblock %}
{% else %} {% else %}
{% block content %} {% block content %}
<div class="col-md-10 col-xxl tasks"> <div class="col-md-9 col-xxl tasks">
{% include '@ChillTask/SingleTask/AccompanyingCourse/list.html.twig' %} {% include '@ChillTask/SingleTask/AccompanyingCourse/list.html.twig' %}
</div> </div>