mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles
This commit is contained in:
commit
0c5e5ac672
@ -21,6 +21,14 @@ and this project adheres to
|
||||
* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities;
|
||||
* Improve badges behaviour with small screens;
|
||||
|
||||
* [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
|
||||
|
||||
### test release 2021-10-04
|
||||
|
@ -7,6 +7,7 @@ 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;
|
||||
@ -22,9 +23,9 @@ final class AsideActivityController extends CRUDController
|
||||
$this->categoryRepository = $categoryRepository;
|
||||
}
|
||||
|
||||
protected function buildQueryEntities(string $action, Request $request)
|
||||
protected function buildQueryEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null)
|
||||
{
|
||||
$qb = parent::buildQueryEntities($action, $request);
|
||||
$qb = parent::buildQueryEntities($action, $request, $filterOrder);
|
||||
|
||||
if ('index' === $action) {
|
||||
$qb->where($qb->expr()->eq('e.agent', ':user'));
|
||||
@ -53,7 +54,7 @@ final class AsideActivityController extends CRUDController
|
||||
$asideActivity = new AsideActivity();
|
||||
|
||||
$duration = $request->query->get('duration', '300');
|
||||
$duration = \DateTime::createFromFormat('U', $duration);
|
||||
$duration = \DateTime::createFromFormat('U', $duration);
|
||||
$asideActivity->setDuration($duration);
|
||||
|
||||
$categoryId = $request->query->get('type', 7);
|
||||
|
@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -1,22 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\ThirdPartyBundle\DataFixtures\ORM;
|
||||
namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyCivility;
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
/**
|
||||
* Class LoadThirdPartyCivility
|
||||
* @package Chill\ThirdPartyBundle\DataFixtures\ORM
|
||||
* @author Mathieu Jaumotte mathieu.jaumotte@champs-libres.coop
|
||||
*/
|
||||
class LoadThirdPartyCivility extends Fixture implements FixtureGroupInterface
|
||||
class LoadCivility extends Fixture implements FixtureGroupInterface
|
||||
{
|
||||
public static function getGroups(): array
|
||||
{
|
||||
return ['thirdparty_civilities'];
|
||||
return ['civilities'];
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
@ -34,8 +29,7 @@ class LoadThirdPartyCivility extends Fixture implements FixtureGroupInterface
|
||||
];
|
||||
|
||||
foreach ( $civilities as $val) {
|
||||
print "Creating thirdparty civility : " . $val['name']['fr'] . "\n";
|
||||
$civility = (new ThirdPartyCivility())
|
||||
$civility = (new Civility())
|
||||
->setName($val['name'])
|
||||
->setActive(true);
|
||||
$manager->persist($civility);
|
@ -20,33 +20,41 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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é <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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é <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Form\Type\Listing;
|
||||
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SearchType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
final class FilterOrderType extends \Symfony\Component\Form\AbstractType
|
||||
{
|
||||
private RequestStack $requestStack;
|
||||
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
174
src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php
Normal file
174
src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -20,23 +20,23 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,12 @@ export default {
|
||||
case 'thirdparty':
|
||||
let data = this.$refs.castThirdparty.$data.thirdparty;
|
||||
data.name = data.text;
|
||||
data.address = { id: data.address.address_id }
|
||||
if (data.address !== undefined) {
|
||||
data.address = { id: data.address.address_id }
|
||||
} else {
|
||||
data.address = null;
|
||||
}
|
||||
|
||||
return data;
|
||||
default:
|
||||
throw Error('Invalid type of entity')
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% set formId = crudMainFormId|default('crud_main_form') %}
|
||||
<div class="{% block crud_content_main_div_class %}{% endblock %}">
|
||||
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
|
||||
{% block crud_content_header %}
|
||||
<h1>{{ ('crud.'~crud_name~'.title_edit')|trans }}</h1>
|
||||
{% endblock crud_content_header %}
|
||||
|
@ -1,9 +1,15 @@
|
||||
<div class="col-10 centered">
|
||||
|
||||
|
||||
{% block index_header %}
|
||||
<h1>{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}</h1>
|
||||
{% 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 %}
|
||||
<p>{{ no_existing_entities_sentences|default('No entities')|trans }}</p>
|
||||
@ -32,17 +38,20 @@
|
||||
|
||||
{% endif %}
|
||||
|
||||
<div class="crud_index__pagination">
|
||||
{{ chill_pagination(paginator) }}
|
||||
</div>
|
||||
{% block pagination %}
|
||||
<div class="crud_index__pagination">
|
||||
{{ chill_pagination(paginator) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block list_actions %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
{% block add_new %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_' ~ crud_name ~ '_new') }}" class="btn btn-new">{{ ('crud.'~crud_name~'.index.add_new')|trans( {'%crud_name%': crud_name} ) }}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% block actions_before %}{% endblock %}
|
||||
{% block add_new %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_' ~ crud_name ~ '_new') }}" class="btn btn-new">{{ ('crud.'~crud_name~'.index.add_new')|trans( {'%crud_name%': crud_name} ) }}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
</ul>
|
||||
{% endblock list_actions %}
|
||||
</div>
|
||||
|
@ -1,10 +1,11 @@
|
||||
{% set formId = crudMainFormId|default('crud_main_form') %}
|
||||
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
|
||||
{% block crud_content_header %}
|
||||
<h1>{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}</h1>
|
||||
{% 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 %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
{% block content_form_actions_back %}
|
||||
@ -25,21 +28,21 @@
|
||||
{% endblock %}
|
||||
{% block content_form_actions_save_and_close %}
|
||||
<li class="">
|
||||
<button type="submit" name="submit" value="save-and-close" class="btn btn-create">
|
||||
<button type="submit" name="submit" value="save-and-close" class="btn btn-create" form="{{ formId }}">
|
||||
{{ 'crud.new.save_and_close'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% block content_form_actions_save_and_show %}
|
||||
<li class="">
|
||||
<button type="submit" name="submit" value="save-and-show" class="btn btn-create">
|
||||
<button type="submit" name="submit" value="save-and-show" class="btn btn-create" form="{{ formId }}">
|
||||
{{ 'crud.new.save_and_show'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% block content_form_actions_save_and_new %}
|
||||
<li class="">
|
||||
<button type="submit" name="submit" value="save-and-new" class="btn btn-create">
|
||||
<button type="submit" name="submit" value="save-and-new" class="btn btn-create" form="{{ formId }}">
|
||||
{{ 'crud.new.save_and_new'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
|
@ -0,0 +1,12 @@
|
||||
{{ form_start(form) }}
|
||||
<div class="chill_filter_order container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="input-group mb-3">
|
||||
{{ form_widget(form.q)}}
|
||||
<button type="submit" class="btn btn-chill-l-gray"><i class="fa fa-search"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
@ -168,6 +168,10 @@
|
||||
{{ form_widget(entry) }}
|
||||
</div>
|
||||
</li>
|
||||
{% else %}
|
||||
<li data-collection-empty-explain="1">
|
||||
<span class="chill-no-data-statement">{{ form.vars.empty_collection_explain|default('No item')|trans }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@ -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 %}
|
||||
|
@ -41,9 +41,8 @@ use Chill\MainBundle\Entity\RoleScope;
|
||||
*
|
||||
* Provides methods for user and entities information.
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
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();
|
||||
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Security\Authorization;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
|
||||
interface AuthorizationHelperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get reachable Centers for the given user, role,
|
||||
* and optionnaly Scope
|
||||
*
|
||||
* @param User $user
|
||||
* @param string|Role $role
|
||||
* @param null|Scope $scope
|
||||
* @return Center[]
|
||||
*/
|
||||
public function getReachableCenters(User $user, string $role, ?Scope $scope = null): array;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $role
|
||||
* @param Center|Center[]|array $center
|
||||
* @return array
|
||||
*/
|
||||
public function getReachableScopes(User $user, string $role, $center): array;
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Templating\Listing;
|
||||
|
||||
use Chill\MainBundle\Form\Type\Listing\FilterOrderType;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class FilterOrderHelper
|
||||
{
|
||||
private FormFactoryInterface $formFactory;
|
||||
private RequestStack $requestStack;
|
||||
private ?array $searchBoxFields = null;
|
||||
|
||||
public function __construct(
|
||||
FormFactoryInterface $formFactory,
|
||||
RequestStack $requestStack
|
||||
) {
|
||||
$this->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));
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Templating\Listing;
|
||||
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class FilterOrderHelperBuilder
|
||||
{
|
||||
private ?array $searchBoxFields = null;
|
||||
private FormFactoryInterface $formFactory;
|
||||
private RequestStack $requestStack;
|
||||
|
||||
public function __construct(
|
||||
FormFactoryInterface $formFactory,
|
||||
RequestStack $requestStack
|
||||
) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Templating\Listing;
|
||||
|
||||
use Symfony\Component\Form\FormFactoryBuilderInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class FilterOrderHelperFactory implements FilterOrderHelperFactoryInterface
|
||||
{
|
||||
private FormFactoryInterface $formFactory;
|
||||
private RequestStack $requestStack;
|
||||
|
||||
public function __construct(
|
||||
FormFactoryInterface $formFactory,
|
||||
RequestStack $requestStack
|
||||
) {
|
||||
$this->formFactory = $formFactory;
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
public function create(string $context, ?array $options = []): FilterOrderHelperBuilder
|
||||
{
|
||||
return new FilterOrderHelperBuilder($this->formFactory, $this->requestStack);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Templating\Listing;
|
||||
|
||||
interface FilterOrderHelperFactoryInterface
|
||||
{
|
||||
public function create(string $context, ?array $options = []): FilterOrderHelperBuilder;
|
||||
}
|
34
src/Bundle/ChillMainBundle/Templating/Listing/Templating.php
Normal file
34
src/Bundle/ChillMainBundle/Templating/Listing/Templating.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Templating\Listing;
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
class Templating extends AbstractExtension
|
||||
{
|
||||
|
||||
public function getFilters()
|
||||
{
|
||||
return [
|
||||
new TwigFilter('chill_render_filter_order_helper', [$this, 'renderFilterOrderHelper'], [
|
||||
'needs_environment' => 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
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
@ -28,18 +28,19 @@ use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Create civility table
|
||||
*/
|
||||
final class Version20211007150019 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'create civility table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->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');
|
||||
}
|
||||
}
|
@ -56,6 +56,7 @@ centers: centres
|
||||
Centers: Centres
|
||||
comment: commentaire
|
||||
Comment: Commentaire
|
||||
Any comment: Aucun commentaire
|
||||
|
||||
# comment embeddable
|
||||
No comment associated: Aucun commentaire
|
||||
|
@ -155,7 +155,7 @@ final class PersonController extends AbstractController
|
||||
$person = $this->_getPerson($person_id);
|
||||
|
||||
if ($person === null) {
|
||||
return $this->createNotFoundException();
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person,
|
||||
|
@ -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,
|
||||
])
|
||||
;
|
||||
|
||||
|
@ -1,37 +1,102 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
|
||||
<span class="name">
|
||||
{{ item.result.text }}
|
||||
<div class="container tpartycontainer">
|
||||
<div class="tparty-identification">
|
||||
<span class="name">
|
||||
{{ item.result.text }}
|
||||
</span>
|
||||
<span class="location">
|
||||
<template v-if="hasAddress">
|
||||
{{ getAddress.text }} -
|
||||
{{ getAddress.postcode.name }}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tpartyparent" v-if="hasParent">
|
||||
<span class="name">
|
||||
{{ item.result.parent.text }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right_actions">
|
||||
|
||||
<span class="badge bg-chill-red" v-if="item.result.kind == 'child'">
|
||||
{{ $t('thirdparty.contact')}}
|
||||
</span>
|
||||
<span class="badge bg-info" v-else-if="item.result.kind == 'company'">
|
||||
{{ $t('thirdparty.company')}}
|
||||
</span>
|
||||
<span class="location">
|
||||
{{ item.result.address.text }} -
|
||||
{{ item.result.address.postcode.name }}
|
||||
<span class="badge bg-secondary" v-else="item.result.kind == 'contact'">
|
||||
{{ $t('thirdparty.contact')}}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div class="right_actions">
|
||||
|
||||
|
||||
<span class="badge rounded-pill bg-secondary" :title="item.key">
|
||||
{{ $t('item.type_thirdparty') }}
|
||||
</span>
|
||||
|
||||
<on-the-fly
|
||||
type="thirdparty"
|
||||
v-bind:id="item.result.id"
|
||||
action="show">
|
||||
type="thirdparty"
|
||||
v-bind:id="item.result.id"
|
||||
action="show">
|
||||
</on-the-fly>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
fr: {
|
||||
thirdparty: {
|
||||
contact: "Contact",
|
||||
company: "Institution",
|
||||
child: "Personne de contact"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'SuggestionThirdParty',
|
||||
components: {
|
||||
OnTheFly
|
||||
},
|
||||
props: ['item']
|
||||
props: ['item'],
|
||||
i18n,
|
||||
computed: {
|
||||
hasAddress() {
|
||||
if (this.$props.item.result.address !== null) {
|
||||
return true;
|
||||
}
|
||||
if (this.$props.item.result.parent !== null) {
|
||||
this.$props.item.result.parent.address !== null;
|
||||
}
|
||||
},
|
||||
hasParent() {
|
||||
return this.$props.item.result.parent !== null;
|
||||
},
|
||||
getAddress() {
|
||||
if (this.$props.item.result.address !== null) {
|
||||
return this.$props.item.result.address;
|
||||
}
|
||||
if (this.$props.item.result.parent.address !== null) {
|
||||
return this.$props.item.result.parent.address;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tpartycontainer {
|
||||
.tpartyparent {
|
||||
.name {
|
||||
font-weight: bold;
|
||||
font-variant: all-small-caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,20 @@
|
||||
</div>
|
||||
<div v-else-if="action === 'edit' || action === 'create'">
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input mt-0" type="radio" v-model="kind" value="company" id="tpartyKindInstitution">
|
||||
<label for="tpartyKindInstitution" class="required">
|
||||
{{ $t('tparty.company')}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input mt-0" type="radio" v-model="kind" value="contact" id="tpartyKindContact">
|
||||
<label for="tpartyKindContact" class="required">
|
||||
{{ $t('tparty.contact')}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control form-control-lg" id="name" v-model="thirdparty.text" v-bind:placeholder="$t('thirdparty.name')" />
|
||||
<label for="name">{{ $t('thirdparty.name') }}</label>
|
||||
@ -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';
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
#}
|
||||
|
||||
{% macro raw(thirdparty, options) %}
|
||||
<span class="name">{{ thirdparty.name }}</span>
|
||||
<span class="name">{{ thirdparty|chill_entity_render_string }}</span>
|
||||
{% endmacro raw %}
|
||||
|
||||
{% macro label(thirdparty, options) %}
|
||||
@ -29,7 +29,7 @@
|
||||
<div class="denomination {{ 'h' ~ options['hLevel'] }}">
|
||||
|
||||
{%- if options['addLink'] and is_granted('CHILL_3PARTY_3PARTY_SHOW', thirdparty) -%}
|
||||
<a href="{{ chill_path_add_return_path('chill_3party_3party_show', { 'thirdparty_id': thirdparty.id }) }}">
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_3party_3party_view', { 'id': thirdparty.isChild ? thirdparty.parent.id : thirdparty.id }) }}">
|
||||
{%- 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) -%}
|
||||
</a>
|
||||
{%- endif -%}
|
||||
@ -81,6 +81,13 @@
|
||||
<div class="item-row entity-bloc">
|
||||
<div class="item-col">
|
||||
{{ _self.label(thirdparty, options) }}
|
||||
{% if thirdparty.kind == 'company' %}
|
||||
<span class="badge bg-info">{{ 'thirdparty.company'|trans }}</span>
|
||||
{% elseif thirdparty.kind == 'child' %}
|
||||
<span class="badge bg-chill-red">{{ 'thirdparty.Child'|trans }}</span>
|
||||
{% elseif thirdparty.kind == 'contact' %}
|
||||
<span class="badge bg-secondary">{{ 'thirdparty.contact'|trans }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
@ -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'] %}
|
||||
<li>
|
||||
<a class="btn btn-sm btn-show" target="_blank" title="{{ 'Show thirdparty'|trans }}"
|
||||
href="{{ path('chill_3party_3party_show', { thirdparty_id: thirdparty.id }) }}"></a>
|
||||
href="{{ path('chill_crud_3party_3party_view', { id: thirdparty.isChild ? thirdparty.parent.id : thirdparty.id }) }}"></a>
|
||||
</li>
|
||||
{% else %}
|
||||
{% endif %}
|
||||
@ -124,4 +131,17 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if options['showContacts'] and thirdparty.activeChildren|length > 0 %}
|
||||
<div class="item-row">
|
||||
{{ '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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
|
63
src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig
vendored
Normal file
63
src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig
vendored
Normal file
@ -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 %}
|
||||
<h2>{{ 'Contacts'|trans }}</h2>
|
||||
{{ form_widget(form.activeChildren) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-3 row">
|
||||
{{ form_label(form.address) }}
|
||||
{{ form_widget(form.address) }}
|
||||
<div class="col-sm-8">
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_row(form.comment) }}
|
||||
{{ form_row(form.centers) }}
|
||||
|
||||
{{ form_row(form.active) }}
|
46
src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig
vendored
Normal file
46
src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
{% block _third_party_active_children_entry_widget %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-3 mb-3">
|
||||
{{ form_widget(form.civility) }}
|
||||
{{ form_errors(form.civility) }}
|
||||
{{ form_label(form.civility) }}
|
||||
</div>
|
||||
<div class="form-group col-md-5 mb-3">
|
||||
{{ form_widget(form.name) }}
|
||||
{{ form_errors(form.name) }}
|
||||
{{ form_label(form.name) }}
|
||||
</div>
|
||||
<div class="form-group col-md-4 mb-3">
|
||||
{{ form_widget(form.profession) }}
|
||||
{{ form_errors(form.profession) }}
|
||||
{{ form_label(form.profession) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-5 mb-3">
|
||||
{{ form_widget(form.telephone) }}
|
||||
{{ form_errors(form.telephone) }}
|
||||
{{ form_label(form.telephone) }}
|
||||
</div>
|
||||
<div class="form-group col-md-5 mb-3">
|
||||
{{ form_widget(form.email) }}
|
||||
{{ form_errors(form.email) }}
|
||||
{{ form_label(form.email) }}
|
||||
</div>
|
||||
<div class="form-group col-md-2 mb-3">
|
||||
{{ form_widget(form.contactDataAnonymous) }}
|
||||
{{ form_label(form.contactDataAnonymous) }}
|
||||
{{ form_errors(form.contactDataAnonymous) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12 mb-3">
|
||||
{{ form_widget(form.comment) }}
|
||||
{{ form_errors(form.comment) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -2,108 +2,59 @@
|
||||
|
||||
{% block title 'List of third parties'|trans %}
|
||||
|
||||
{% set third_parties = entities %}
|
||||
|
||||
{% block content %}
|
||||
<div class="thirdparty-list my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||
{% block index_header %}
|
||||
<h1>{{ 'List of third parties'|trans }}</h1>
|
||||
{% endblock %}
|
||||
|
||||
<h1>{{ 'List of third parties'|trans }}</h1>
|
||||
{% block table_entities %}
|
||||
<div class="thirdparty-list my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div>
|
||||
|
||||
{% if third_parties|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'No third parties'|trans }}</p>
|
||||
{% else %}
|
||||
<label class="counter">
|
||||
<span>{{ paginator.totalItems }}</span> {{ 'third parties'|trans }}
|
||||
</label>
|
||||
|
||||
<nav class="filter-actions border border-secondary my-4 p-3">
|
||||
<i>outils de filtrage</i>
|
||||
</nav>
|
||||
<div class="flex-table">
|
||||
{% for tp in third_parties %}
|
||||
<div class="item-bloc">
|
||||
{{ tp|chill_entity_render_box({'render': 'bloc', 'addLink': false, 'showContacts': true }) }}
|
||||
<div class="item-row separator">
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', tp) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_3party_3party_edit',
|
||||
{ 'id': (tp.isParent ? tp.id : tp.parent.id) }) }}" class="btn btn-sm btn-update"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_3PARTY_3PARTY_SHOW', tp) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_3party_3party_view',
|
||||
{ 'id': (tp.isParent ? tp.id : tp.parent.id) }) }}" class="btn btn-sm btn-show"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<label class="counter">
|
||||
<span>{{ pagination.totalItems }}</span> {{ 'third parties'|trans }}
|
||||
</label>
|
||||
|
||||
<table class="table table-bordered border-dark table-striped align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="chill-pink" style="width: 35px;"></th>
|
||||
<th class="chill-pink">{{ 'Name'|trans }}
|
||||
<i class="fa fa-fw fa-sort"></i>
|
||||
</th>
|
||||
<th class="chill-pink">{{ 'Category'|trans }}
|
||||
<i class="fa fa-fw fa-sort"></i>
|
||||
</th>
|
||||
<th class="chill-pink">{{ 'Address'|trans }}
|
||||
<i class="fa fa-fw fa-sort"></i>
|
||||
</th>
|
||||
<th class="chill-pink">{{ 'thirdparty.UpdatedAt.short'|trans }}
|
||||
<i class="fa fa-fw fa-sort"></i>
|
||||
</th>
|
||||
<th class="chill-pink"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tp in third_parties %}
|
||||
<tr>
|
||||
<th>{{ (tp.active ? '<i class="fa fa-check chill-green">' : '<i class="fa fa-times chill-red">')|raw }}</th>
|
||||
<td>{{ tp.name }}</td>
|
||||
{% set types = [] %}
|
||||
{% for t in tp.types %}
|
||||
{% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<td>{{ types|join(', ') }}</td>
|
||||
<td>
|
||||
{{ tp.address|chill_entity_render_box({'multiline': false, 'with_valid_from': false}) }}
|
||||
</td>
|
||||
<td>
|
||||
{% if tp.updatedAt != null %}
|
||||
{{ tp.updatedAt|format_date('short') }}
|
||||
{% else %}
|
||||
{{ tp.createdAt|format_date('short') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', tp) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_3party_3party_update', { 'thirdparty_id': tp.id }) }}" class="btn btn-sm btn-update"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_3PARTY_3PARTY_SHOW', tp) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_3party_3party_show', { 'thirdparty_id': tp.id }) }}" class="btn btn-sm btn-show"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if third_parties|length < pagination.getTotalItems %}
|
||||
{{ chill_pagination(pagination, 'long') }}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
{{ chill_items_per_page(pagination) }}
|
||||
</li>
|
||||
{% if is_granted('CHILL_3PARTY_3PARTY_CREATE') %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_3party_3party_new') }}" class="btn btn-create">
|
||||
{{ "New third party"|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block actions_before %}
|
||||
<li class="cancel">
|
||||
{{ chill_items_per_page(paginator) }}
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('page_3party_3party_index') }}
|
||||
{% endblock %}
|
||||
|
@ -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 %}
|
||||
<div class="thirdparty-new my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<h1>{{ 'Create third party'|trans }}</h1>
|
||||
|
||||
{{ 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) }}
|
||||
|
||||
<div class="mb-3 row">
|
||||
{{ form_label(form.address) }}
|
||||
{{ form_widget(form.address) }}
|
||||
<div class="col-sm-8">
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_row(form.comment) }}
|
||||
{{ form_row(form.centers) }}
|
||||
|
||||
{{ form_row(form.active) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_return_path_or('chill_3party_3party_index') }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.submit, {'label': 'Create', 'attr': {'class': 'btn btn-new' }}) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
</div>
|
||||
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
|
||||
{% block crud_content_header %}
|
||||
<h1>{{ 'Create third party'|trans }}</h1>
|
||||
{% endblock %}
|
||||
{% block crud_content_form_rows %}
|
||||
{% include '@ChillThirdParty/ThirdParty/_form.html.twig' %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
37
src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new_pick_kind.html.twig
vendored
Normal file
37
src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new_pick_kind.html.twig
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title 'thirdparty.Which kind of third party ?'|trans %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-10 centered">
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
<div class="container" style="margin-top: 2rem;">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<a
|
||||
href="{{ chill_path_forward_return_path('chill_crud_3party_3party_new', {'kind': 'company'}) }}"
|
||||
class="btn btn-outline-chill-green-dark">
|
||||
{{ 'thirdparty.A company'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<p>{{ 'thirdparty.a_company_explanation'|trans }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<a
|
||||
href="{{ chill_path_forward_return_path('chill_crud_3party_3party_new', {'kind': 'contact'}) }}"
|
||||
class="btn btn-outline-chill-green-dark">
|
||||
{{ 'thirdparty.A contact'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<p>{{ 'thirdparty.a_contact_explanation'|trans }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -1,122 +0,0 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% set title_ = 'Show third party %name%'|trans({'%name%' : thirdParty.name }) %}
|
||||
|
||||
{% block title title_ %}
|
||||
|
||||
{% block content %}
|
||||
<div class="thirdparty-show my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<h1>
|
||||
{{ title_ }}
|
||||
<span class="badge bg-{{ thirdParty.active ? 'success' : 'danger' }}"
|
||||
title="{{ (thirdParty.active ? 'shown to users' : 'not shown to users')|trans }}">
|
||||
{{ (thirdParty.active ? 'Active' : 'Inactive')|trans }}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
|
||||
<dl class="chill_view_data">
|
||||
|
||||
<dt>{{ 'Name'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.isLeaf == true %}{{ thirdParty.civility }}{% endif %}
|
||||
{{ thirdParty.name }}
|
||||
</dd>
|
||||
|
||||
{% if thirdParty.isLeaf == false %}
|
||||
<dt>{{ 'thirdparty.NameCompany'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.nameCompany == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No nameCompany given'|trans }}</span>
|
||||
{% else %}
|
||||
{{ thirdParty.nameCompany }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'thirdparty.Acronym'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.acronym == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No acronym given'|trans }}</span>
|
||||
{% else %}
|
||||
{{ thirdParty.acronym }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{{ 'Type'|trans }}</dt>
|
||||
{% set types = [] %}
|
||||
{% for t in thirdParty.types %}
|
||||
{% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %}
|
||||
{% endfor %}
|
||||
<dd>
|
||||
{{ types|join(', ') }}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Phonenumber'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.telephone == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No phone given'|trans }}</span>
|
||||
{% else %}
|
||||
<a href="{{ 'tel:' ~ thirdParty.telephone }}">
|
||||
{{ thirdParty.telephone|chill_print_or_message("thirdparty.No_phonenumber") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'email'|trans }}<dt>
|
||||
<dd>
|
||||
{% if thirdParty.email == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No email given'|trans }}</span>
|
||||
{% else %}
|
||||
<a href="{{ 'mailto:' ~ thirdParty.email }}">
|
||||
{{ thirdParty.email|chill_print_or_message("thirdparty.No_email") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Address'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.address == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{% else %}
|
||||
{{ thirdParty.address|chill_entity_render_box({'with_valid_from': false, 'extended_infos': true }) }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Comment'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.comment is not empty %}
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ thirdParty.comment|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Centers'|trans }}</dt>
|
||||
<dd>{{ 'The party is visible in those centers'|trans }} : {{ thirdParty.centers|join(', ') }}</dd>
|
||||
|
||||
</dl>
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a class="btn btn-cancel" href="{{ chill_return_path_or('chill_3party_3party_index') }}">
|
||||
{{ 'Back to the list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', thirdParty) %}
|
||||
<li>
|
||||
<a class="btn btn-update" href="{{ chill_path_forward_return_path('chill_3party_3party_update', { 'thirdparty_id': thirdParty.id }) }}">
|
||||
{{ 'Update'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -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 %}
|
||||
<div class="thirdparty-new my-5">
|
||||
<div class="row justify-content-center">
|
||||
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
|
||||
{% block crud_content_header %}
|
||||
<h1>
|
||||
{{ 'Update third party %name%'|trans({ '%name%': thirdParty.name }) }}
|
||||
<span class="badge bg-{{ thirdParty.active ? 'success' : 'danger' }}"
|
||||
title="{{ (thirdParty.active ? 'shown to users' : 'not shown to users')|trans }}">
|
||||
{{ (thirdParty.active ? 'Active' : 'Inactive')|trans }}
|
||||
</span>
|
||||
</h1>
|
||||
{% endblock %}
|
||||
{% block crud_content_form_rows %}
|
||||
<div class="date-by">
|
||||
{% 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 %}
|
||||
</div>
|
||||
{% include '@ChillThirdParty/ThirdParty/_form.html.twig' %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content_not %}
|
||||
<div class="thirdparty-edit my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10">
|
||||
|
||||
<h1>
|
||||
{{ 'Update third party %name%'|trans({ '%name%': thirdParty.name }) }}
|
||||
<span class="badge bg-{{ thirdParty.active ? 'success' : 'danger' }}"
|
||||
title="{{ (thirdParty.active ? 'shown to users' : 'not shown to users')|trans }}">
|
||||
{{ (thirdParty.active ? 'Active' : 'Inactive')|trans }}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div class="date-by">
|
||||
{% 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 %}
|
||||
</div>
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
|
139
src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig
vendored
Normal file
139
src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig
vendored
Normal file
@ -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 %}
|
||||
<div class="thirdparty-show my-5">
|
||||
<div class="row justify-content-center">
|
||||
{% embed '@ChillMain/CRUD/_view_content.html.twig' %}
|
||||
{% block crud_content_header %}
|
||||
<h1>
|
||||
{{ title_ }}
|
||||
<span class="badge bg-{{ thirdParty.active ? 'success' : 'danger' }}"
|
||||
title="{{ (thirdParty.active ? 'shown to users' : 'not shown to users')|trans }}">
|
||||
{{ (thirdParty.active ? 'Active' : 'Inactive')|trans }}
|
||||
</span>
|
||||
</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block crud_content_view_details %}
|
||||
<dl class="chill_view_data">
|
||||
|
||||
<dt>{{ 'Name'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.isLeaf == true %}{{ thirdParty.civility }}{% endif %}
|
||||
{{ thirdParty.name }}
|
||||
</dd>
|
||||
|
||||
{% if thirdParty.kind == 'company' %}
|
||||
<dt>{{ 'thirdparty.NameCompany'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.nameCompany == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No nameCompany given'|trans }}</span>
|
||||
{% else %}
|
||||
{{ thirdParty.nameCompany }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'thirdparty.Acronym'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.acronym == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No acronym given'|trans }}</span>
|
||||
{% else %}
|
||||
{{ thirdParty.acronym }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{{ 'Type'|trans }}</dt>
|
||||
{% set types = [] %}
|
||||
{% for t in thirdParty.types %}
|
||||
{% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %}
|
||||
{% endfor %}
|
||||
<dd>
|
||||
{{ types|join(', ') }}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Phonenumber'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.telephone == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No phone given'|trans }}</span>
|
||||
{% else %}
|
||||
<a href="{{ 'tel:' ~ thirdParty.telephone }}">
|
||||
{{ thirdParty.telephone|chill_print_or_message("thirdparty.No_phonenumber") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'email'|trans }}<dt>
|
||||
<dd>
|
||||
{% if thirdParty.email == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No email given'|trans }}</span>
|
||||
{% else %}
|
||||
<a href="{{ 'mailto:' ~ thirdParty.email }}">
|
||||
{{ thirdParty.email|chill_print_or_message("thirdparty.No_email") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Address'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.address == null %}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{% else %}
|
||||
{{ thirdParty.address|chill_entity_render_box({'with_valid_from': false, 'extended_infos': true }) }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Comment'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.comment is not empty %}
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ thirdParty.comment|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'Any comment'|trans }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
{% if thirdParty.kind == 'company' %}
|
||||
<dt>{{ 'Contacts'|trans }}</dt>
|
||||
<dd>
|
||||
{% if thirdParty.activeChildren|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'Any contacts associated'|trans }}</p>
|
||||
{% else %}
|
||||
<div class="flex-table">
|
||||
{% for tp in thirdParty.activeChildren %}
|
||||
<div class="item-bloc">
|
||||
{{ tp|chill_entity_render_box({'render': 'bloc', 'addLink': false}) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{{ 'Centers'|trans }}</dt>
|
||||
<dd>
|
||||
{% 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 %}
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
{% endblock %}
|
||||
{% block content_form_actions_delete %}{% endblock %}
|
||||
{% block content_view_actions_duplicate_link %}{% endblock %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
);
|
||||
};
|
||||
|
@ -6,8 +6,3 @@ services:
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: 64 }
|
||||
|
||||
Chill\ThirdPartyBundle\Repository\:
|
||||
autowire: true
|
||||
resource: '../Repository/'
|
||||
tags:
|
||||
- { name: 'doctrine.repository_service' }
|
||||
|
@ -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']
|
||||
Chill\ThirdPartyBundle\Controller\:
|
||||
resource: './../Controller'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@ -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 }
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
services:
|
||||
Chill\ThirdPartyBundle\Repository\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../Repository/'
|
||||
|
||||
Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepositoryInterface: '@Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepository'
|
@ -1,6 +1,6 @@
|
||||
services:
|
||||
Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender:
|
||||
arguments:
|
||||
$engine: '@Symfony\Component\Templating\EngineInterface'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
tags:
|
||||
- 'chill.render_entity'
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\ThirdParty;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Add created_by to third party
|
||||
*/
|
||||
final class Version20211006200924 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add link to creator (created_by) to thirdParty';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->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');
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\ThirdParty;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* migrate data from 3party.civility to chill_main_civility table
|
||||
*/
|
||||
final class Version20211007150459 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'migrate data from 3party.civility to chill_main_civility table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->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)');
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\ThirdParty;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Create trigger for canonicalisation on 3party + indexes
|
||||
*/
|
||||
final class Version20211007165001 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create trigger for canonicalisation on 3party + indexes';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->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
|
||||
");
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\ThirdParty;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Add anonymous flag for contacts
|
||||
*/
|
||||
final class Version20211007194942 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add anonymous flag for contacts';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->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');
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user