mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
handle types and categories in a single select input
This commit is contained in:
parent
9eec15873e
commit
88d073b9a9
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Chill\ThirdPartyBundle;
|
||||
|
||||
use Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeProviderInterface;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Chill\ThirdPartyBundle\DependencyInjection\CompilerPass\ThirdPartyTypeCompilerPass;
|
||||
|
||||
@ -10,6 +11,8 @@ class ChillThirdPartyBundle extends Bundle
|
||||
public function build(\Symfony\Component\DependencyInjection\ContainerBuilder $container)
|
||||
{
|
||||
parent::build($container);
|
||||
$container->registerForAutoconfiguration(ThirdPartyTypeProviderInterface::class)
|
||||
->addTag('chill_3party.provider');
|
||||
$container->addCompilerPass(new ThirdPartyTypeCompilerPass());
|
||||
}
|
||||
|
||||
|
@ -127,6 +127,8 @@ final class ThirdPartyController extends CRUDController
|
||||
return $this->getFilterOrderHelperFactory()
|
||||
->create(self::class)
|
||||
->addSearchBox(['name', 'company_name', 'acronym'])
|
||||
//->addToggle('only-active', [])
|
||||
// ->addOrderBy()
|
||||
->build();
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
public function setTypes(array $type = null)
|
||||
{
|
||||
// remove all keys from the input data
|
||||
$this->type = \array_values($type);
|
||||
$this->types = \array_values($type);
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->setTypes($type);
|
||||
@ -387,6 +387,40 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
public function addType(?string $type): self
|
||||
{
|
||||
if (NULL === $type) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!\in_array($type, $this->types ?? [])) {
|
||||
$this->types[] = $type;
|
||||
}
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->addType($type);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeType(?string $type): self
|
||||
{
|
||||
if (NULL === $type) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (\in_array($type, $this->types ?? [])) {
|
||||
$this->types = \array_filter($this->types, fn($e) => !\in_array($e, $this->types));
|
||||
}
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->removeType($type);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
@ -541,7 +575,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
}
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->addCategory($child);
|
||||
$child->addCategory($category);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -556,7 +590,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
$this->categories->removeElement($category);
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->removeCategory($child);
|
||||
$child->removeCategory($category);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -631,6 +665,72 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addTypesAndCategories($typeAndCategory): self
|
||||
{
|
||||
if ($typeAndCategory instanceof ThirdPartyCategory) {
|
||||
$this->addCategory($typeAndCategory);
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($typeAndCategory)) {
|
||||
$this->addType($typeAndCategory);
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
"typeAndCategory should be a string or a %s", ThirdPartyCategory::class));
|
||||
}
|
||||
|
||||
public function removeTypesAndCategories($typeAndCategory): self
|
||||
{
|
||||
if ($typeAndCategory instanceof ThirdPartyCategory) {
|
||||
$this->removeCategory($typeAndCategory);
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($typeAndCategory)) {
|
||||
$this->removeType($typeAndCategory);
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
"typeAndCategory should be a string or a %s", ThirdPartyCategory::class));
|
||||
}
|
||||
|
||||
public function getTypesAndCategories(): array
|
||||
{
|
||||
return \array_merge(
|
||||
$this->getCategories()->toArray(),
|
||||
$this->getTypes() ?? []
|
||||
);
|
||||
}
|
||||
|
||||
public function setTypesAndCategories(array $typesAndCategories): self
|
||||
{
|
||||
$types = \array_filter($typesAndCategories, fn($item) => !$item instanceof ThirdPartyCategory);
|
||||
$this->setTypes($types);
|
||||
|
||||
// handle categories
|
||||
foreach ($typesAndCategories as $t) {
|
||||
$this->addTypesAndCategories($t);
|
||||
}
|
||||
|
||||
$categories = \array_filter($typesAndCategories, fn($item) => $item instanceof ThirdPartyCategory);
|
||||
$categoriesHashes = \array_map(fn(ThirdPartyCategory $c) => \spl_object_hash($c), $categories);
|
||||
|
||||
foreach ($categories as $c) {
|
||||
$this->addCategory($c);
|
||||
}
|
||||
|
||||
foreach ($this->getCategories() as $t) {
|
||||
if (!\in_array(\spl_object_hash($t), $categoriesHashes)) {
|
||||
$this->removeCategory($t);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ThirdParty $child
|
||||
|
@ -11,9 +11,12 @@ use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyProfession;
|
||||
use Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType;
|
||||
use Chill\ThirdPartyBundle\Form\Type\PickThirdPartyTypeCategoryType;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
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;
|
||||
@ -40,14 +43,14 @@ class ThirdPartyType extends AbstractType
|
||||
|
||||
protected TranslatableStringHelper $translatableStringHelper;
|
||||
|
||||
protected ObjectManager $om;
|
||||
protected EntityManagerInterface $om;
|
||||
|
||||
public function __construct(
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
ThirdPartyTypeManager $typesManager,
|
||||
TranslatableStringHelper $translatableStringHelper,
|
||||
ObjectManager $om
|
||||
EntityManagerInterface $om
|
||||
) {
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
@ -171,21 +174,9 @@ class ThirdPartyType extends AbstractType
|
||||
}
|
||||
|
||||
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('typesAndCategories', PickThirdPartyTypeCategoryType::class, [
|
||||
'label' => 'thirdparty.Categories'
|
||||
])
|
||||
->add('active', ChoiceType::class, [
|
||||
'label' => 'thirdparty.Status',
|
||||
@ -196,42 +187,6 @@ class ThirdPartyType extends AbstractType
|
||||
'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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\ThirdPartyBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory;
|
||||
use Chill\ThirdPartyBundle\Repository\ThirdPartyCategoryRepository;
|
||||
use Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class PickThirdPartyTypeCategoryType extends \Symfony\Component\Form\AbstractType
|
||||
{
|
||||
private ThirdPartyCategoryRepository $thirdPartyCategoryRepository;
|
||||
private ThirdPartyTypeManager $thirdPartyTypeManager;
|
||||
private TranslatableStringHelper $translatableStringHelper;
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private const PREFIX_TYPE = 'chill_3party.key_label.';
|
||||
|
||||
public function __construct(
|
||||
ThirdPartyCategoryRepository $thirdPartyCategoryRepository,
|
||||
ThirdPartyTypeManager $thirdPartyTypeManager,
|
||||
TranslatableStringHelper $translatableStringHelper,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->thirdPartyCategoryRepository = $thirdPartyCategoryRepository;
|
||||
$this->thirdPartyTypeManager = $thirdPartyTypeManager;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$choices = \array_merge(
|
||||
$this->thirdPartyCategoryRepository->findBy(['active' => true]),
|
||||
$this->thirdPartyTypeManager->getTypes()
|
||||
);
|
||||
|
||||
\uasort($choices, function ($itemA, $itemB) {
|
||||
$strA = $itemA instanceof ThirdPartyCategory ? $this->translatableStringHelper
|
||||
->localize($itemA->getName()) : $this->translator->trans(self::PREFIX_TYPE.$itemA);
|
||||
$strB = $itemB instanceof ThirdPartyCategory ? $this->translatableStringHelper
|
||||
->localize($itemB->getName()) : $this->translator->trans(self::PREFIX_TYPE.$itemB);
|
||||
|
||||
return $strA <=> $strB;
|
||||
});
|
||||
|
||||
|
||||
$resolver->setDefaults([
|
||||
'choices' => $choices,
|
||||
'attr' => [ 'class' => 'select2' ],
|
||||
'multiple' => true,
|
||||
'choice_label' => function($item) {
|
||||
if ($item instanceof ThirdPartyCategory) {
|
||||
return $this->translatableStringHelper->localize($item->getName());
|
||||
}
|
||||
return self::PREFIX_TYPE.$item;
|
||||
},
|
||||
'choice_value' => function($item) {
|
||||
return $this->reverseTransform($item);
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
public function reverseTransform($value)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
if (is_array($value)){
|
||||
$r = [];
|
||||
|
||||
foreach ($value as $v) {
|
||||
$r[] = $this->transform($v);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
if ($value instanceof ThirdPartyCategory) {
|
||||
return 'category:'.$value->getId();
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return 'type:'.$value;
|
||||
}
|
||||
|
||||
throw new UnexpectedTypeException($value, \implode(' or ', ['array', 'string', ThirdPartyCategory::class]));
|
||||
}
|
||||
}
|
@ -14,8 +14,7 @@
|
||||
{{ form_row(form.profession) }}
|
||||
{% endif %}
|
||||
|
||||
{{ form_row(form.types) }}
|
||||
{{ form_row(form.categories) }}
|
||||
{{ form_row(form.typesAndCategories) }}
|
||||
|
||||
{{ form_row(form.telephone) }}
|
||||
{{ form_row(form.email) }}
|
||||
|
@ -48,13 +48,20 @@
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{{ 'Type'|trans }}</dt>
|
||||
<dt>{{ 'thirdparty.Categories'|trans }}</dt>
|
||||
{% set types = [] %}
|
||||
{% for t in thirdParty.types %}
|
||||
{% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %}
|
||||
{% endfor %}
|
||||
{% for c in thirdParty.categories %}
|
||||
{% set types = types|merge([ c.name|localize_translatable_string ]) %}
|
||||
{% endfor %}
|
||||
<dd>
|
||||
{{ types|join(', ') }}
|
||||
{% if types|length > 0 %}
|
||||
{{ types|join(', ') }}
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'thirdParty.Any categories' }}</p>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Phonenumber'|trans }}</dt>
|
||||
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\ThirdParty\Tests\Entity;
|
||||
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ThirdPartyTest extends TestCase
|
||||
{
|
||||
public function testAddingRemovingActivityTypes()
|
||||
{
|
||||
$tp = new ThirdParty();
|
||||
$cat1 = new ThirdPartyCategory();
|
||||
$cat2 = new ThirdPartyCategory();
|
||||
|
||||
$tp->addTypesAndCategories('type');
|
||||
$tp->addTypesAndCategories($cat1);
|
||||
$tp->addTypesAndCategories($cat2);
|
||||
|
||||
$this->assertTrue($tp->getCategories()->contains($cat1));
|
||||
$this->assertTrue($tp->getCategories()->contains($cat2));
|
||||
$this->assertCount(2, $tp->getCategories());
|
||||
|
||||
$this->assertCount(1, $tp->getTypes());
|
||||
$this->assertContains('type', $tp->getTypes());
|
||||
|
||||
$this->assertCount(3, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat1, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat2, $tp->getTypesAndCategories());
|
||||
$this->assertContains('type', $tp->getTypesAndCategories());
|
||||
|
||||
// remove type
|
||||
$tp->removeTypesAndCategories('type');
|
||||
$tp->removeTypesAndCategories($cat2);
|
||||
|
||||
$this->assertTrue($tp->getCategories()->contains($cat1),
|
||||
"test that cat1 is still present");
|
||||
$this->assertFalse($tp->getCategories()->contains($cat2));
|
||||
$this->assertCount(1, $tp->getCategories());
|
||||
|
||||
$this->assertCount(0, $tp->getTypes());
|
||||
$this->assertNotContains('type', $tp->getTypes());
|
||||
|
||||
$this->assertCount(1, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat1, $tp->getTypesAndCategories());
|
||||
$this->assertNotContains($cat2, $tp->getTypesAndCategories());
|
||||
$this->assertNotContains('type', $tp->getTypesAndCategories());
|
||||
}
|
||||
|
||||
public function testSyncingActivityTypes()
|
||||
{
|
||||
$tp = new ThirdParty();
|
||||
$tp->setTypesAndCategories([
|
||||
'type1',
|
||||
'type2',
|
||||
$cat1 = new ThirdPartyCategory(),
|
||||
$cat2 = new ThirdPartyCategory()
|
||||
]);
|
||||
|
||||
$this->assertTrue($tp->getCategories()->contains($cat1));
|
||||
$this->assertTrue($tp->getCategories()->contains($cat2));
|
||||
$this->assertCount(2, $tp->getCategories());
|
||||
|
||||
$this->assertCount(2, $tp->getTypes());
|
||||
$this->assertContains('type1', $tp->getTypes());
|
||||
$this->assertContains('type2', $tp->getTypes());
|
||||
|
||||
$this->assertCount(4, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat1, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat2, $tp->getTypesAndCategories());
|
||||
$this->assertContains('type1', $tp->getTypesAndCategories());
|
||||
$this->assertContains('type2', $tp->getTypesAndCategories());
|
||||
|
||||
$tp->setTypesAndCategories([$cat1, 'type1']);
|
||||
|
||||
$this->assertTrue($tp->getCategories()->contains($cat1));
|
||||
$this->assertFalse($tp->getCategories()->contains($cat2));
|
||||
$this->assertCount(1, $tp->getCategories());
|
||||
|
||||
$this->assertCount(1, $tp->getTypes());
|
||||
$this->assertContains('type1', $tp->getTypes());
|
||||
$this->assertNotContains('type2', $tp->getTypes());
|
||||
|
||||
$this->assertCount(2, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat1, $tp->getTypesAndCategories());
|
||||
$this->assertNotContains($cat2, $tp->getTypesAndCategories());
|
||||
$this->assertContains('type1', $tp->getTypesAndCategories());
|
||||
$this->assertNotContains('type2', $tp->getTypesAndCategories());
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +1,5 @@
|
||||
services:
|
||||
Chill\ThirdPartyBundle\Form\ThirdPartyType:
|
||||
arguments:
|
||||
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||
$typesManager: '@Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager'
|
||||
$translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
|
||||
$om: '@doctrine.orm.entity_manager'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType:
|
||||
arguments:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
$urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
$typesManager: '@Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
Chill\ThirdPartyBundle\Form\:
|
||||
resource: '../../Form/'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@ -67,6 +67,7 @@ No nameCompany given: Aucune raison sociale renseignée
|
||||
No acronym given: Aucun sigle renseigné
|
||||
No phone given: Aucun téléphone renseigné
|
||||
No email given: Aucune adresse courriel renseignée
|
||||
thirdparty.Any categories: Aucune catégorie
|
||||
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user