diff --git a/src/Bundle/ChillThirdPartyBundle/ChillThirdPartyBundle.php b/src/Bundle/ChillThirdPartyBundle/ChillThirdPartyBundle.php index ba184292a..6c6ff08c9 100644 --- a/src/Bundle/ChillThirdPartyBundle/ChillThirdPartyBundle.php +++ b/src/Bundle/ChillThirdPartyBundle/ChillThirdPartyBundle.php @@ -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()); } diff --git a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php index d6e4c0046..f7d47a32d 100644 --- a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php +++ b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php @@ -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(); } } diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 38124b645..69da6cf50 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -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 diff --git a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php index 057f848cb..3d30b9e81 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php @@ -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' - ]); - } } } diff --git a/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php new file mode 100644 index 000000000..2af962895 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php @@ -0,0 +1,100 @@ +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])); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig index ef347eb6d..87d26cc22 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig @@ -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) }} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig index 460468e98..6b6395b19 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig @@ -48,13 +48,20 @@ {% endif %} -
{{ 'Type'|trans }}
+
{{ 'thirdparty.Categories'|trans }}
{% 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 %}
- {{ types|join(', ') }} + {% if types|length > 0 %} + {{ types|join(', ') }} + {% else %} +

{{ 'thirdParty.Any categories' }}

+ {% endif %}
{{ 'Phonenumber'|trans }}
diff --git a/src/Bundle/ChillThirdPartyBundle/Tests/Entity/ThirdPartyTest.php b/src/Bundle/ChillThirdPartyBundle/Tests/Entity/ThirdPartyTest.php new file mode 100644 index 000000000..ac1758030 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Tests/Entity/ThirdPartyTest.php @@ -0,0 +1,92 @@ +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()); + } + +} diff --git a/src/Bundle/ChillThirdPartyBundle/config/services/form.yaml b/src/Bundle/ChillThirdPartyBundle/config/services/form.yaml index 8ff604769..0ed703e86 100644 --- a/src/Bundle/ChillThirdPartyBundle/config/services/form.yaml +++ b/src/Bundle/ChillThirdPartyBundle/config/services/form.yaml @@ -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 diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index e6167840d..ba5847c0b 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -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