diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2172dbd03..4b6da4216 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ before_script: # Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service # See http://docs.gitlab.com/ee/ci/services/README.html for examples. services: - - name: postgres:12 + - name: postgis/postgis:12-3.1-alpine alias: db - name: redis alias: redis diff --git a/composer.json b/composer.json index 4181af68a..95074c18f 100644 --- a/composer.json +++ b/composer.json @@ -73,7 +73,8 @@ "symfony/web-profiler-bundle": "^5.0", "symfony/var-dumper": "4.*", "symfony/debug-bundle": "^5.1", - "symfony/phpunit-bridge": "^5.2" + "symfony/phpunit-bridge": "^5.2", + "nelmio/alice": "^3.8" }, "scripts": { "auto-scripts": { diff --git a/docs/source/development/api.rst b/docs/source/development/api.rst index 86eb6ff65..a5fef2372 100644 --- a/docs/source/development/api.rst +++ b/docs/source/development/api.rst @@ -13,6 +13,9 @@ API Chill provides a basic framework to build REST api. +Basic configuration +******************* + Configure a route ================= @@ -34,7 +37,7 @@ You can also: * `How to create your custom normalizer `_ Auto-loading the routes -*********************** +======================= Ensure that those lines are present in your file `app/config/routing.yml`: @@ -47,7 +50,7 @@ Ensure that those lines are present in your file `app/config/routing.yml`: Create your model -***************** +================= Create your model on the usual way: @@ -87,7 +90,7 @@ Create your model on the usual way: Configure api -************* +============= Configure the api using Yaml (see the full configuration: :ref:`api_full_configuration`): @@ -171,7 +174,7 @@ Configure the api using Yaml (see the full configuration: :ref:`api_full_configu } The :code:`_index` and :code:`_entity` action -============================================= +********************************************* The :code:`_index` and :code:`_entity` action are default actions: @@ -189,7 +192,7 @@ Entity: Path: :code:`/api/1.0/person/accompanying-period/origin/{id}.{_format}` Role -==== +**** By default, the key `base_role` is used to check ACL. Take care of creating the :code:`Voter` required to take that into account. @@ -216,7 +219,7 @@ You can also define a role for each method. In this case, this role is used for HEAD: MY ROLE_SEE Customize the controller -======================== +************************ You can customize the controller by hooking into the default actions. Take care of extending :code:`Chill\MainBundle\CRUD\Controller\ApiController`. @@ -264,7 +267,7 @@ And set your controller in configuration: HEAD: true Create your own actions -======================= +*********************** You can add your own actions: @@ -361,8 +364,297 @@ Then, create the corresponding action into your controller: } } +Managing association +******************** + +ManyToOne association +===================== + +In ManyToOne association, you can add associated entities using the :code:`PATCH` request. By default, the serializer deserialize entities only with their id and discriminator type, if any. + +Example: + +.. code-block:: bash + + curl -X 'PATCH' \ + 'http://localhost:8001/api/1.0/person/accompanying-course/2668.json' \ + -H 'accept: */*' \ + -H 'Content-Type: application/json' \ + # see the data sent to the server: \ + -d '{ + "type": "accompanying_period", + "id": 2668, + "origin": { "id": 11 } + }' + +ManyToMany associations +======================= + +In OneToMany association, you can easily create route for adding and removing entities, using :code:`POST` and :code:`DELETE` requests. + +Prepare your entity, creating the methods :code:`addYourEntity` and :code:`removeYourEntity`: + +.. code-block:: php + + namespace Chill\PersonBundle\Entity; + + use Chill\MainBundle\Entity\Scope; + use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\Common\Collections\Collection; + use Doctrine\ORM\Mapping as ORM; + use Symfony\Component\Serializer\Annotation\Groups; + use Symfony\Component\Serializer\Annotation\DiscriminatorMap; + + /** + * AccompanyingPeriod Class + * + * @ORM\Entity + * @ORM\Table(name="chill_person_accompanying_period") + * @DiscriminatorMap(typeProperty="type", mapping={ + * "accompanying_period"=AccompanyingPeriod::class + * }) + */ + class AccompanyingPeriod + { + /** + * @var Collection + * @ORM\ManyToMany( + * targetEntity=Scope::class, + * cascade={} + * ) + * @Groups({"read"}) + */ + private $scopes; + + public function addScope(Scope $scope): self + { + $this->scopes[] = $scope; + + return $this; + } + + public function removeScope(Scope $scope): void + { + $this->scopes->removeElement($scope); + } + + +Create your route into the configuration: + +.. code-block:: yaml + + chill_main: + apis: + - + class: Chill\PersonBundle\Entity\AccompanyingPeriod + name: accompanying_course + base_path: /api/1.0/person/accompanying-course + controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController + actions: + scope: + methods: + POST: true + DELETE: true + GET: false + HEAD: false + PUT: false + PATCH: false + roles: + POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + GET: null + HEAD: null + PUT: null + PATCH: null + controller_action: null + path: null + single-collection: single + +This will create a new route, which will accept two methods: DELETE and POST: + +.. code-block:: raw + + +--------------+---------------------------------------------------------------------------------------+ + | Property | Value | + +--------------+---------------------------------------------------------------------------------------+ + | Route Name | chill_api_single_accompanying_course_scope | + | Path | /api/1.0/person/accompanying-course/{id}/scope.{_format} | + | Path Regex | {^/api/1\.0/person/accompanying\-course/(?P[^/]++)/scope\.(?P<_format>[^/]++)$}sD | + | Host | ANY | + | Host Regex | | + | Scheme | ANY | + | Method | POST|DELETE | + | Requirements | {id}: \d+ | + | Class | Symfony\Component\Routing\Route | + | Defaults | _controller: csapi_accompanying_course_controller:scopeApi | + | Options | compiler_class: Symfony\Component\Routing\RouteCompiler | + +--------------+---------------------------------------------------------------------------------------+ + + + +Then, create the controller action. Call the method: + +.. code-block:: php + + namespace Chill\PersonBundle\Controller; + + use Chill\MainBundle\CRUD\Controller\ApiController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + use Chill\MainBundle\Entity\Scope; + + class MyController extends ApiController + { + public function scopeApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('scope', $id, $request, $_format, 'scope', Scope::class, [ 'groups' => [ 'read' ] ]); + } + } + +This will allow to add a scope by his id, and delete them. + +Curl requests: + +.. code-block:: bash + + # add a scope with id 5 + curl -X 'POST' \ + 'http://localhost:8001/api/1.0/person/accompanying-course/2868/scope.json' \ + -H 'accept: */*' \ + -H 'Content-Type: application/json' \ + -d '{ + "type": "scope", + "id": 5 + }' + + # remove a scope with id 5 + curl -X 'DELETE' \ + 'http://localhost:8001/api/1.0/person/accompanying-course/2868/scope.json' \ + -H 'accept: */*' \ + -H 'Content-Type: application/json' \ + -d '{ + "id": 5, + "type": "scope" + }' + +Deserializing an association where multiple types are allowed +============================================================= + +Sometimes, multiples types are allowed as association to one entity: + +.. code-block:: php + + namespace Chill\PersonBundle\Entity\AccompanyingPeriod; + + use Chill\PersonBundle\Entity\Person; + use Chill\ThirdPartyBundle\Entity\ThirdParty; + use Doctrine\ORM\Mapping as ORM; + + class Resource + { + + + /** + * @ORM\ManyToOne(targetEntity=ThirdParty::class) + * @ORM\JoinColumn(nullable=true) + */ + private $thirdParty; + + /** + * @ORM\ManyToOne(targetEntity=Person::class) + * @ORM\JoinColumn(nullable=true) + */ + private $person; + + + /** + * + * @param $resource Person|ThirdParty + */ + public function setResource($resource): self + { + // ... + } + + + /** + * @return ThirdParty|Person + * @Groups({"read", "write"}) + */ + public function getResource() + { + return $this->person ?? $this->thirdParty; + } + } + +This is not well taken into account by the Symfony serializer natively. + +You must, then, create your own CustomNormalizer. You can help yourself using this: + +.. code-block:: php + + namespace Chill\PersonBundle\Serializer\Normalizer; + + use Chill\PersonBundle\Entity\Person; + use Chill\ThirdPartyBundle\Entity\ThirdParty; + use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; + use Chill\PersonBundle\Repository\AccompanyingPeriod\ResourceRepository; + use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; + use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; + use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait; + use Symfony\Component\Serializer\Exception; + use Chill\MainBundle\Serializer\Normalizer\DiscriminatedObjectDenormalizer; + + + class AccompanyingPeriodResourceNormalizer implements DenormalizerInterface, DenormalizerAwareInterface + { + use DenormalizerAwareTrait; + use ObjectToPopulateTrait; + + public function __construct(ResourceRepository $repository) + { + $this->repository = $repository; + } + + public function denormalize($data, string $type, string $format = null, array $context = []) + { + // .. snipped for brevity + + if ($resource === NULL) { + $resource = new Resource(); + } + + if (\array_key_exists('resource', $data)) { + $res = $this->denormalizer->denormalize( + $data['resource'], + // call for a "multiple type" + DiscriminatedObjectDenormalizer::TYPE, + $format, + // into the context, we add the list of allowed types: + [ + DiscriminatedObjectDenormalizer::ALLOWED_TYPES => + [ + Person::class, ThirdParty::class + ] + ] + ); + + $resource->setResource($res); + } + + return $resource; + } + + + public function supportsDenormalization($data, string $type, string $format = null) + { + return $type === Resource::class; + } + } + Serialization for collection -============================ +**************************** A specific model has been defined for returning collection: @@ -381,8 +673,9 @@ A specific model has been defined for returning collection: } } +Where this is relevant, this model should be re-used in custom controller actions. -This can be achieved quickly by assembling results into a :code:`Chill\MainBundle\Serializer\Model\Collection`. The pagination information is given by using :code:`Paginator` (see :ref:`Pagination `). +In custom actions, this can be achieved quickly by assembling results into a :code:`Chill\MainBundle\Serializer\Model\Collection`. The pagination information is given by using :code:`Paginator` (see :ref:`Pagination `). .. code-block:: php @@ -400,10 +693,11 @@ This can be achieved quickly by assembling results into a :code:`Chill\MainBundl } } + .. _api_full_configuration: Full configuration example -========================== +************************** .. code-block:: yaml diff --git a/docs/source/installation/index.rst b/docs/source/installation/index.rst index 6aee5c526..1312480e3 100644 --- a/docs/source/installation/index.rst +++ b/docs/source/installation/index.rst @@ -82,7 +82,7 @@ Chill will be available at ``http://localhost:8001.`` Currently, there isn't any .. code-block:: bash - docker-compose exec --user $(id -u) php bin/console doctrine:fixtures:load + docker-compose exec --user $(id -u) php bin/console doctrine:fixtures:load --purge-with-truncate There are several users available: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9d74dd61a..ab9e69052 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -18,11 +18,10 @@ src/Bundle/ChillMainBundle/Tests/ - diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php index 5cc055f26..71476fb78 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php @@ -5,7 +5,7 @@ namespace Chill\MainBundle\CRUD\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Validator\Validator\ValidatorInterface; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorInterface; @@ -25,12 +25,33 @@ class AbstractCRUDController extends AbstractController * * @param string $id * @return object + * @throw Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the object is not found */ - protected function getEntity($action, $id, Request $request): ?object + protected function getEntity($action, $id, Request $request): object { - return $this->getDoctrine() + $e = $this->getDoctrine() ->getRepository($this->getEntityClass()) ->find($id); + + if (NULL === $e) { + throw $this->createNotFoundException(sprintf("The object %s for id %s is not found", $this->getEntityClass(), $id)); + } + + return $e; + } + + /** + * Create an entity. + * + * @param string $action + * @param Request $request + * @return object + */ + protected function createEntity(string $action, Request $request): object + { + $type = $this->getEntityClass(); + + return new $type; } /** @@ -222,4 +243,9 @@ class AbstractCRUDController extends AbstractController { return $this->container->get('chill_main.paginator_factory'); } + + protected function getValidator(): ValidatorInterface + { + return $this->get('validator'); + } } diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php index 7643db0e9..9686a7b98 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php @@ -8,6 +8,10 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Serializer\SerializerInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Pagination\PaginatorInterface; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; class ApiController extends AbstractCRUDController { @@ -38,11 +42,6 @@ class ApiController extends AbstractCRUDController return $postFetch; } - if (NULL === $entity) { - throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found", $this->getCrudName(), $id)); - } - $response = $this->checkACL($action, $request, $_format, $entity); if ($response instanceof Response) { return $response; @@ -81,12 +80,186 @@ class ApiController extends AbstractCRUDController { switch ($request->getMethod()) { case Request::METHOD_GET: - case REQUEST::METHOD_HEAD: + case Request::METHOD_HEAD: return $this->entityGet('_entity', $request, $id, $_format); + case Request::METHOD_PUT: + case Request::METHOD_PATCH: + return $this->entityPut('_entity', $request, $id, $_format); + case Request::METHOD_POST: + return $this->entityPostAction('_entity', $request, $id, $_format); default: throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented"); } } + public function entityPost(Request $request, $_format): Response + { + switch($request->getMethod()) { + case Request::METHOD_POST: + return $this->entityPostAction('_entity', $request, $_format); + default: + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented"); + } + } + + protected function entityPostAction($action, Request $request, string $_format): Response + { + $entity = $this->createEntity($action, $request); + + try { + $entity = $this->deserialize($action, $request, $_format, $entity); + } catch (NotEncodableValueException $e) { + throw new BadRequestException("invalid json", 400, $e); + } + + $errors = $this->validate($action, $request, $_format, $entity); + + $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors); + if ($response instanceof Response) { + return $response; + } + + if ($errors->count() > 0) { + $response = $this->json($errors); + $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); + + return $response; + } + + $response = $this->checkACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $this->getDoctrine()->getManager()->persist($entity); + $this->getDoctrine()->getManager()->flush(); + + $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors); + if ($response instanceof Response) { + return $response; + } + $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + return $this->json( + $entity, + Response::HTTP_OK, + [], + $this->getContextForSerializationPostAlter($action, $request, $_format, $entity) + ); + } + public function entityPut($action, Request $request, $id, string $_format): Response + { + $entity = $this->getEntity($action, $id, $request, $_format); + + $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format); + if ($postFetch instanceof Response) { + return $postFetch; + } + + if (NULL === $entity) { + throw $this->createNotFoundException(sprintf("The %s with id %s " + . "is not found", $this->getCrudName(), $id)); + } + + $response = $this->checkACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + try { + $entity = $this->deserialize($action, $request, $_format, $entity); + } catch (NotEncodableValueException $e) { + throw new BadRequestException("invalid json", 400, $e); + } + + $errors = $this->validate($action, $request, $_format, $entity); + + $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors); + if ($response instanceof Response) { + return $response; + } + + if ($errors->count() > 0) { + $response = $this->json($errors); + $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); + + return $response; + } + + $this->getDoctrine()->getManager()->flush(); + + $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors); + if ($response instanceof Response) { + return $response; + } + + return $this->json( + $entity, + Response::HTTP_OK, + [], + $this->getContextForSerializationPostAlter($action, $request, $_format, $entity) + ); + } + + protected function onAfterValidation(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response + { + return null; + } + + + protected function onAfterFlush(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response + { + return null; + } + + protected function getValidationGroups(string $action, Request $request, string $_format, $entity): ?array + { + return null; + } + + protected function validate(string $action, Request $request, string $_format, $entity, array $more = []): ConstraintViolationListInterface + { + $validationGroups = $this->getValidationGroups($action, $request, $_format, $entity); + + return $this->getValidator()->validate($entity, null, $validationGroups); + } + + /** + * Deserialize the content of the request into the class associated with the curd + */ + protected function deserialize(string $action, Request $request, string $_format, $entity = null): object + { + $default = []; + + if (NULL !== $entity) { + $default[AbstractNormalizer::OBJECT_TO_POPULATE] = $entity; + } + + $context = \array_merge( + $default, + $this->getContextForSerialization($action, $request, $_format, $entity) + ); + + return $this->getSerializer()->deserialize($request->getContent(), $this->getEntityClass(), $_format, $context); + } + /** * Base action for indexing entities @@ -172,6 +345,110 @@ class ApiController extends AbstractCRUDController return $this->serializeCollection($action, $request, $_format, $paginator, $entities); } + + /** + * Add or remove an associated entity, using `add` and `remove` methods. + * + * This method: + * + * 1. Fetch the base entity (throw 404 if not found) + * 2. checkACL, + * 3. run onPostCheckACL, return response if any, + * 4. deserialize posted data into the entity given by $postedDataType, with the context in $postedDataContext + * 5. run 'add+$property' for POST method, or 'remove+$property' for DELETE method + * 6. validate the base entity (not the deserialized one). Groups are fetched from getValidationGroups, validation is perform by `validate` + * 7. run onAfterValidation + * 8. if errors, return a 422 response with errors + * 9. flush the data + * 10. run onAfterFlush + * 11. return a 202 response for DELETE with empty body, or HTTP 200 for post with serialized posted entity + * + * @param string action + * @param mixed id + * @param Request $request + * @param string $_format + * @param string $property the name of the property. This will be used to make a `add+$property` and `remove+$property` method + * @param string $postedDataType the type of the posted data (the content) + * @param string $postedDataContext a context to deserialize posted data (the content) + * @throw BadRequestException if unable to deserialize the posted data + * @throw BadRequestException if the method is not POST or DELETE + * + */ + protected function addRemoveSomething(string $action, $id, Request $request, string $_format, string $property, string $postedDataType, $postedDataContext = []): Response + { + $entity = $this->getEntity($action, $id, $request); + + $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format); + if ($postFetch instanceof Response) { + return $postFetch; + } + + $response = $this->checkACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + try { + $postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext); + } catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) { + throw new BadRequestException(sprintf("Unable to deserialize posted ". + "data: %s", $e->getMessage()), 0, $e); + } + + switch ($request->getMethod()) { + case Request::METHOD_DELETE: + // oups... how to use property accessor to remove element ? + $entity->{'remove'.\ucfirst($property)}($postedData); + break; + case Request::METHOD_POST: + $entity->{'add'.\ucfirst($property)}($postedData); + break; + default: + throw new BadRequestException("this method is not supported"); + } + + $errors = $this->validate($action, $request, $_format, $entity, [$postedData]); + + $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors, [$postedData]); + if ($response instanceof Response) { + return $response; + } + + if ($errors->count() > 0) { + // only format accepted + return $this->json($errors, 422); + } + + $this->getDoctrine()->getManager()->flush(); + + + $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors, [$postedData]); + if ($response instanceof Response) { + return $response; + } + + switch ($request->getMethod()) { + case Request::METHOD_DELETE: + return $this->json('', Response::HTTP_OK); + case Request::METHOD_POST: + return $this->json( + $postedData, + Response::HTTP_OK, + [], + $this->getContextForSerializationPostAlter($action, $request, $_format, $entity, [$postedData]) + ); + } + } /** * Serialize collections @@ -189,7 +466,27 @@ class ApiController extends AbstractCRUDController protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array { - return []; + switch ($request->getMethod()) { + case Request::METHOD_GET: + return [ 'groups' => [ 'read' ]]; + case Request::METHOD_PUT: + case Request::METHOD_PATCH: + case Request::METHOD_POST: + return [ 'groups' => [ 'write' ]]; + default: + throw new \LogicException("get context for serialization is not implemented for this method"); + } + } + + /** + * Get the context for serialization post alter query (in case of + * PATCH, PUT, or POST method) + * + * This is called **after** the entity was altered. + */ + protected function getContextForSerializationPostAlter(string $action, Request $request, string $_format, $entity, array $more = []): array + { + return [ 'groups' => [ 'read' ]]; } /** diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php index 8338af8c6..25b7e5860 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php @@ -43,16 +43,16 @@ use Symfony\Component\Serializer\SerializerInterface; */ class CRUDController extends AbstractController { - + /** - * The crud configuration + * The crud configuration * * This configuration si defined by `chill_main['crud']`. * * @var array */ protected $crudConfig; - + /** * @param array $config */ @@ -60,7 +60,7 @@ class CRUDController extends AbstractController { $this->crudConfig = $config; } - + /** * @param $parameter * @return Response @@ -69,7 +69,7 @@ class CRUDController extends AbstractController { return new Response($parameter); } - + /** * @param Request $request * @param $id @@ -79,7 +79,7 @@ class CRUDController extends AbstractController { return $this->deleteAction('delete', $request, $id); } - + /** * @param string $action * @param Request $request @@ -90,78 +90,78 @@ class CRUDController extends AbstractController protected function deleteAction(string $action, Request $request, $id, $formClass = null) { $this->onPreDelete($action, $request, $id); - + $entity = $this->getEntity($action, $id, $request); - + $postFetch = $this->onPostFetchEntity($action, $request, $entity); - + if ($postFetch instanceof Response) { return $postFetch; } - + if (NULL === $entity) { throw $this->createNotFoundException(sprintf("The %s with id %s " . "is not found"), $this->getCrudName(), $id); } - + $response = $this->checkACL($action, $entity); if ($response instanceof Response) { return $response; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - + $form = $this->createFormFor($action, $entity, $formClass); - + $form->handleRequest($request); - + if ($form->isSubmitted() && $form->isValid()) { $this->onFormValid($entity, $form, $request); $em = $this->getDoctrine()->getManager(); - + $this->onPreRemove($action, $entity, $form, $request); $this->removeEntity($action, $entity, $form, $request); $this->onPostRemove($action, $entity, $form, $request); - + $this->onPreFlush($action, $entity, $form, $request); $em->flush(); $this->onPostFlush($action, $entity, $form, $request); - + $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); - + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); - + if ($result instanceof Response) { return $result; } - + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]); - + } elseif ($form->isSubmitted()) { $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } - + $defaultTemplateParameters = [ 'form' => $form->createView(), 'entity' => $entity, 'crud_name' => $this->getCrudName() ]; - + return $this->render( - $this->getTemplateFor($action, $entity, $request), + $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } - + /** * @param string $action * @param Request $request */ protected function onPreDelete(string $action, Request $request) {} - + /** * @param string $action * @param $entity @@ -169,7 +169,7 @@ class CRUDController extends AbstractController * @param Request $request */ protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request) {} - + /** * @param string $action * @param $entity @@ -177,7 +177,7 @@ class CRUDController extends AbstractController * @param Request $request */ protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request) {} - + /** * @param string $action * @param $entity @@ -190,10 +190,10 @@ class CRUDController extends AbstractController ->getManager() ->remove($entity); } - + /** * Base method called by index action. - * + * * @param Request $request * @return type */ @@ -201,14 +201,14 @@ class CRUDController extends AbstractController { return $this->indexEntityAction('index', $request); } - + /** * Build an index page. - * + * * Some steps may be overriden during this process of rendering. - * + * * 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 @@ -219,15 +219,15 @@ class CRUDController extends AbstractController * x. fetch the results, using `getQueryResult` * x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it * 4. create default parameters: - * - * The default parameters are: - * + * + * 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` + * 5. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. - * + * * @param string $action * @param Request $request * @return type @@ -235,80 +235,80 @@ class CRUDController extends AbstractController protected function indexEntityAction($action, Request $request) { $this->onPreIndex($action, $request); - + $response = $this->checkACL($action, null); if ($response instanceof Response) { return $response; } - + if (!isset($entity)) { $entity = ''; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - + $totalItems = $this->countEntities($action, $request); $paginator = $this->getPaginatorFactory()->create($totalItems); - - $response = $this->onPreIndexBuildQuery($action, $request, $totalItems, + + $response = $this->onPreIndexBuildQuery($action, $request, $totalItems, $paginator); - + if ($response instanceof Response) { return $response; } - + $query = $this->queryEntities($action, $request, $paginator); - - $response = $this->onPostIndexBuildQuery($action, $request, $totalItems, + + $response = $this->onPostIndexBuildQuery($action, $request, $totalItems, $paginator, $query); - + if ($response instanceof Response) { return $response; } - + $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $query); - - $response = $this->onPostIndexFetchQuery($action, $request, $totalItems, + + $response = $this->onPostIndexFetchQuery($action, $request, $totalItems, $paginator, $entities); - + if ($response instanceof Response) { return $response; } - + $defaultTemplateParameters = [ 'entities' => $entities, 'crud_name' => $this->getCrudName(), 'paginator' => $paginator ]; - + return $this->render( - $this->getTemplateFor($action, $entities, $request), + $this->getTemplateFor($action, $entities, $request), $this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters) ); } - + /** * @param string $action * @param Request $request */ protected function onPreIndex(string $action, Request $request) { } - + /** * method used by indexAction - * + * * @param string $action * @param Request $request * @param int $totalItems * @param PaginatorInterface $paginator */ protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) { } - + /** * method used by indexAction - * + * * @param string $action * @param Request $request * @param int $totalItems @@ -316,10 +316,10 @@ class CRUDController extends AbstractController * @param mixed $query */ protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) { } - + /** * method used by indexAction - * + * * @param string $action * @param Request $request * @param int $totalItems @@ -327,14 +327,14 @@ class CRUDController extends AbstractController * @param mixed $entities */ protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $entities) { } - + /** * Build the base query for listing all entities, normally use in a listing * page. - * + * * This base query does not contains any `WHERE` or `SELECT` clauses. Those * are added by other methods, like `queryEntities` and `countQueries`. - * + * * @param string $action * @param Request $request * @return QueryBuilder @@ -347,16 +347,16 @@ class CRUDController extends AbstractController ->from($this->getEntityClass(), 'e') ; } - + /** * Query the entity. - * + * * By default, get all entities. - * + * * The method `orderEntity` is called internally to order entities. - * + * * It returns, by default, a query builder. - * + * * @param string $action * @param Request $request * @param PaginatorInterface $paginator @@ -367,15 +367,15 @@ class CRUDController extends AbstractController $query = $this->buildQueryEntities($action, $request) ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) ->setMaxResults($paginator->getItemsPerPage()); - + // allow to order queries and return the new query return $this->orderQuery($action, $query, $request, $paginator); } - - + + /** * Add ordering fields in the query build by self::queryEntities - * + * * @param string $action * @param QueryBuilder|mixed $query by default, an instance of QueryBuilder * @param Request $request @@ -386,10 +386,10 @@ class CRUDController extends AbstractController { return $query; } - + /** * Get the result of the query - * + * * @param string $action * @param Request $request * @param int $totalItems @@ -397,14 +397,14 @@ class CRUDController extends AbstractController * @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, $query) { return $query->getQuery()->getResult(); } - + /** * Count the number of entities - * + * * @param string $action * @param Request $request * @return int @@ -417,12 +417,12 @@ class CRUDController extends AbstractController ->getSingleScalarResult() ; } - + /** * BAse method for edit action - * + * * IMplemented by the method formEditAction, with action as 'edit' - * + * * @param Request $request * @param mixed $id * @return Response @@ -431,12 +431,12 @@ class CRUDController extends AbstractController { return $this->formEditAction('edit', $request, $id); } - + /** * Base method for new action - * + * * Implemented by the method formNewAction, with action as 'new' - * + * * @param Request $request * @return Response */ @@ -444,12 +444,12 @@ class CRUDController extends AbstractController { return $this->formCreateAction('new', $request); } - + /** * Base method for the view action. - * + * * Implemented by the method viewAction, with action as 'view' - * + * * @param Request $request * @param mixed $id * @return Response @@ -458,28 +458,28 @@ class CRUDController extends AbstractController { return $this->viewAction('view', $request, $id); } - + /** * The view action. - * + * * Some steps may be overriden during this process of rendering: - * + * * This method: - * + * * 1. fetch the entity, using `getEntity` * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, * this response is returned. * 2. throw an HttpNotFoundException if entity is null * 3. check ACL using `checkACL` ; - * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * 4. launch `onPostCheckACL`. If the result is an instance of Response, * this response is returned ; * 5. generate default template parameters: - * + * * * `entity`: the fetched entity ; * * `crud_name`: the crud name - * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. - * + * * @param string $action * @param Request $request * @param mixed $id @@ -488,23 +488,23 @@ class CRUDController extends AbstractController protected function viewAction(string $action, Request $request, $id, $_format = 'html'): Response { $entity = $this->getEntity($action, $id, $request); - + $postFetch = $this->onPostFetchEntity($action, $request, $entity); - + if ($postFetch instanceof Response) { return $postFetch; } - + if (NULL === $entity) { throw $this->createNotFoundException(sprintf("The %s with id %s " . "is not found", $this->getCrudName(), $id)); } - + $response = $this->checkACL($action, $entity); if ($response instanceof Response) { return $response; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; @@ -515,9 +515,9 @@ class CRUDController extends AbstractController 'entity' => $entity, 'crud_name' => $this->getCrudName() ]; - + return $this->render( - $this->getTemplateFor($action, $entity, $request), + $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } elseif ($_format === 'json') { @@ -537,45 +537,45 @@ class CRUDController extends AbstractController { return []; } - - + + /** * The edit action. - * + * * Some steps may be overriden during this process of rendering: - * + * * This method: - * + * * 1. fetch the entity, using `getEntity` * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, * this response is returned. * 2. throw an HttpNotFoundException if entity is null * 3. check ACL using `checkACL` ; - * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * 4. launch `onPostCheckACL`. If the result is an instance of Response, * this response is returned ; * 5. generate a form using `createFormFor`, and handle request on this form; - * + * * If the form is valid, the entity is stored and flushed, and a redirection * is returned. - * + * * In this case, those hooks are available: - * + * * * onFormValid * * onPreFlush * * onPostFlush - * * onBeforeRedirectAfterSubmission. If this method return an instance of + * * onBeforeRedirectAfterSubmission. If this method return an instance of * Response, this response is returned. - * + * * 5. generate default template parameters: - * + * * * `entity`: the fetched entity ; * * `crud_name`: the crud name ; * * `form`: the formview instance. - * - * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. - * + * * @param string $action * @param Request $request * @param mixed $id @@ -587,98 +587,98 @@ class CRUDController extends AbstractController protected function formEditAction(string $action, Request $request, $id, string $formClass = null, array $formOptions = []): Response { $entity = $this->getEntity($action, $id, $request); - + if (NULL === $entity) { throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found"), $this->getCrudName(), $id); + . "is not found", $this->getCrudName(), $id)); } - + $response = $this->checkACL($action, $entity); if ($response instanceof Response) { return $response; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - + $form = $this->createFormFor($action, $entity, $formClass, $formOptions); - + $form->handleRequest($request); - + if ($form->isSubmitted() && $form->isValid()) { $this->onFormValid($entity, $form, $request); $em = $this->getDoctrine()->getManager(); - + $this->onPreFlush($action, $entity, $form, $request); $em->flush(); $this->onPostFlush($action, $entity, $form, $request); - + $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); - + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); - + if ($result instanceof Response) { return $result; } - + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'); - + } elseif ($form->isSubmitted()) { $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } - + $defaultTemplateParameters = [ 'form' => $form->createView(), 'entity' => $entity, 'crud_name' => $this->getCrudName() ]; - + return $this->render( - $this->getTemplateFor($action, $entity, $request), + $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } - + /** * The new (or creation) action. - * + * * Some steps may be overriden during this process of rendering: - * + * * This method: - * + * * 1. Create or duplicate an entity: - * - * If the `duplicate` parameter is present, the entity is duplicated + * + * If the `duplicate` parameter is present, the entity is duplicated * using the `duplicate` method. - * + * * If not, the entity is created using the `create` method. * 3. check ACL using `checkACL` ; - * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * 4. launch `onPostCheckACL`. If the result is an instance of Response, * this response is returned ; * 5. generate a form using `createFormFor`, and handle request on this form; - * + * * If the form is valid, the entity is stored and flushed, and a redirection * is returned. - * + * * In this case, those hooks are available: - * + * * * onFormValid * * onPreFlush * * onPostFlush - * * onBeforeRedirectAfterSubmission. If this method return an instance of + * * onBeforeRedirectAfterSubmission. If this method return an instance of * Response, this response is returned. - * + * * 5. generate default template parameters: - * + * * * `entity`: the fetched entity ; * * `crud_name`: the crud name ; * * `form`: the formview instance. - * - * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. - * + * * @param string $action * @param Request $request * @param type $formClass @@ -691,62 +691,62 @@ class CRUDController extends AbstractController } else { $entity = $this->createEntity($action, $request); } - + $response = $this->checkACL($action, $entity); if ($response instanceof Response) { return $response; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - + $form = $this->createFormFor($action, $entity, $formClass); - + $form->handleRequest($request); - + if ($form->isSubmitted() && $form->isValid()) { $this->onFormValid($entity, $form, $request); $em = $this->getDoctrine()->getManager(); - + $this->onPrePersist($action, $entity, $form, $request); $em->persist($entity); $this->onPostPersist($action, $entity, $form, $request); - + $this->onPreFlush($action, $entity, $form, $request); $em->flush(); $this->onPostFlush($action, $entity, $form, $request); $this->getPaginatorFactory(); $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); - + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); - + if ($result instanceof Response) { return $result; } - + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]); - + } elseif ($form->isSubmitted()) { $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } - + $defaultTemplateParameters = [ 'form' => $form->createView(), 'entity' => $entity, 'crud_name' => $this->getCrudName() ]; - + return $this->render( - $this->getTemplateFor($action, $entity, $request), + $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } - + /** * get the instance of the entity with the given id - * + * * @param string $id * @return object */ @@ -756,10 +756,10 @@ class CRUDController extends AbstractController ->getRepository($this->getEntityClass()) ->find($id); } - + /** * Duplicate an entity - * + * * @param string $action * @param Request $request * @return mixed @@ -768,24 +768,24 @@ class CRUDController extends AbstractController { $id = $request->query->get('duplicate_id', 0); $originalEntity = $this->getEntity($action, $id, $request); - + $this->getDoctrine()->getManager() ->detach($originalEntity); - + return $originalEntity; } - + /** - * + * * @return string the complete fqdn of the class */ protected function getEntityClass(): string { return $this->crudConfig['class']; } - + /** - * + * * @return string the crud name */ protected function getCrudName(): string @@ -795,13 +795,13 @@ class CRUDController extends AbstractController /** * check the acl. Called by every action. - * - * By default, check the role given by `getRoleFor` for the value given in + * + * By default, check the role given by `getRoleFor` for the value given in * $entity. - * + * * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException * if not accessible. - * + * * @param string $action * @param mixed $entity * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException @@ -810,10 +810,10 @@ class CRUDController extends AbstractController { $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity); } - + /** * get the role given from the config. - * + * * @param string $action * @return string */ @@ -822,27 +822,27 @@ class CRUDController extends AbstractController if (\array_key_exists('role', $this->getActionConfig($action))) { return $this->getActionConfig($action)['role']; } - + return $this->buildDefaultRole($action); } /** * build a default role name, using the crud resolver. - * + * * This method should not be overriden. Override `getRoleFor` instead. - * + * * @param string $action * @return string */ protected function buildDefaultRole($action) { - return $this->getCrudResolver()->buildDefaultRole($this->getCrudName(), + return $this->getCrudResolver()->buildDefaultRole($this->getCrudName(), $action); } - + /** * get the default form class from config - * + * * @param string $action * @return string the FQDN of the form class */ @@ -852,27 +852,27 @@ class CRUDController extends AbstractController return $this->crudConfig[$action]['form_class'] ?? $this->getDefaultDeleteFormClass($action); } - + return $this->crudConfig[$action]['form_class'] ?? $this->crudConfig['form_class']; } - + protected function getDefaultDeleteFormClass($action) { return CRUDDeleteEntityForm::class; } - + /** * Create a form - * + * * use the method `getFormClassFor` - * + * * A hook is available: `customizeForm` allow you to customize the form * if needed. - * + * * It is preferable to override customizeForm instead of overriding * this method. - * + * * @param string $action * @param mixed $entity * @param string $formClass @@ -882,17 +882,17 @@ class CRUDController extends AbstractController protected function createFormFor(string $action, $entity, string $formClass = null, array $formOptions = []): FormInterface { $formClass = $formClass ?? $this->getFormClassFor($action); - + $form = $this->createForm($formClass, $entity, $formOptions); - + $this->customizeForm($action, $form); - + return $form; } - + /** * Customize the form created by createFormFor. - * + * * @param string $action * @param FormInterface $form */ @@ -900,12 +900,12 @@ class CRUDController extends AbstractController { } - + /** * Generate a message which explains an error about the form. - * + * * Used in form actions - * + * * @param string $action * @param FormInterface $form * @return string @@ -913,13 +913,13 @@ class CRUDController extends AbstractController protected function generateFormErrorMessage(string $action, FormInterface $form): string { $msg = 'This form contains errors'; - + return $this->getTranslator()->trans($msg); } - + /** * Generate a success message when a form could be flushed successfully - * + * * @param string $action * @param mixed $entity * @return string @@ -939,13 +939,13 @@ class CRUDController extends AbstractController default: $msg = "crud.default.success"; } - + return $this->getTranslator()->trans($msg); } - + /** * Customize template parameters. - * + * * @param string $action * @param mixed $entity * @param Request $request @@ -953,9 +953,9 @@ class CRUDController extends AbstractController * @return array */ protected function generateTemplateParameter( - string $action, - $entity, - Request $request, + string $action, + $entity, + Request $request, array $defaultTemplateParameters = [] ) { return $defaultTemplateParameters; @@ -963,7 +963,7 @@ class CRUDController extends AbstractController /** * Create an entity. - * + * * @param string $action * @param Request $request * @return object @@ -971,17 +971,17 @@ class CRUDController extends AbstractController protected function createEntity(string $action, Request $request): object { $type = $this->getEntityClass(); - + return new $type; } - + /** * Get the template for the current crud. - * - * This template may be defined in configuration. If any template are - * defined, return the default template for the actions new, edit, index, + * + * This template may be defined in configuration. If any template are + * defined, return the default template for the actions new, edit, index, * and view. - * + * * @param string $action * @param mixed $entity the entity for the current request, or an array of entities * @param Request $request @@ -993,11 +993,11 @@ class CRUDController extends AbstractController if ($this->hasCustomTemplate($action, $entity, $request)) { return $this->getActionConfig($action)['template']; } - + switch ($action) { case 'new': return '@ChillMain/CRUD/new.html.twig'; - case 'edit': + case 'edit': return '@ChillMain/CRUD/edit.html.twig'; case 'index': return '@ChillMain/CRUD/index.html.twig'; @@ -1011,7 +1011,7 @@ class CRUDController extends AbstractController . "action"); } } - + /** * @param $action * @param $entity @@ -1022,7 +1022,7 @@ class CRUDController extends AbstractController { return !empty($this->getActionConfig($action)['template']); } - + /** * @param string $action * @param $entity @@ -1032,7 +1032,7 @@ class CRUDController extends AbstractController protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) { } - + /** * @param string $action * @param $entity @@ -1042,7 +1042,7 @@ class CRUDController extends AbstractController protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) { } - + /** * @param string $action * @param $entity @@ -1052,7 +1052,7 @@ class CRUDController extends AbstractController protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) { } - + /** * @param string $action * @param $entity @@ -1062,7 +1062,7 @@ class CRUDController extends AbstractController protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) { } - + /** * @param $action * @param Request $request @@ -1073,7 +1073,7 @@ class CRUDController extends AbstractController { return null; } - + /** * @param $action * @param Request $request @@ -1084,7 +1084,7 @@ class CRUDController extends AbstractController { return null; } - + /** * @param object $entity * @param FormInterface $form @@ -1093,16 +1093,16 @@ class CRUDController extends AbstractController protected function onFormValid(object $entity, FormInterface $form, Request $request) { } - + /** * Return a redirect response depending on the value of submit button. - * + * * The handled values are : - * + * * * save-and-close: return to index of current crud ; * * save-and-new: return to new page of current crud ; * * save-and-view: return to view page of current crud ; - * + * * @param string $action * @param mixed $entity * @param FormInterface $form @@ -1124,7 +1124,7 @@ class CRUDController extends AbstractController ]); } } - + /** * Include services * @@ -1135,7 +1135,7 @@ class CRUDController extends AbstractController { return $this->crudConfig['actions'][$action]; } - + /** * @return PaginatorFactory */ @@ -1143,7 +1143,7 @@ class CRUDController extends AbstractController { return $this->container->get('chill_main.paginator_factory'); } - + /** * @return TranslatorInterface */ @@ -1151,7 +1151,7 @@ class CRUDController extends AbstractController { return $this->container->get('translator'); } - + /** * @return AuthorizationHelper */ @@ -1159,19 +1159,19 @@ class CRUDController extends AbstractController { return $this->container->get(AuthorizationHelper::class); } - + /** * @param Role $role * @param Scope|null $scope * @return \Chill\MainBundle\Entity\Center[] */ - protected function getReachableCenters(Role $role, Scope $scope = null) + protected function getReachableCenters(Role $role, Scope $scope = null) { return $this->getAuthorizationHelper() ->getReachableCenters($this->getUser(), $role, $scope) ; } - + /** * @return EventDispatcherInterface */ @@ -1179,7 +1179,7 @@ class CRUDController extends AbstractController { return $this->get(EventDispatcherInterface::class); } - + /** * @return Resolver */ @@ -1187,7 +1187,7 @@ class CRUDController extends AbstractController { return $this->get(Resolver::class); } - + /** * @return array */ diff --git a/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php b/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php index 32068e518..9d61d6233 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php +++ b/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php @@ -183,48 +183,26 @@ class CRUDRoutesLoader extends Loader $methods = \array_keys(\array_filter($action['methods'], function($value, $key) { return $value; }, ARRAY_FILTER_USE_BOTH)); - $route = new Route($path, $defaults, $requirements); - $route->setMethods($methods); - - $collection->add('chill_api_single_'.$crudConfig['name'].'_'.$name, $route); - } + if (count($methods) === 0) { + throw new \RuntimeException("The api configuration named \"{$crudConfig['name']}\", action \"{$name}\", ". + "does not have any allowed methods. You should remove this action from the config ". + "or allow, at least, one method"); + } - return $collection; - } + if ('_entity' === $name && \in_array(Request::METHOD_POST, $methods)) { + unset($methods[\array_search(Request::METHOD_POST, $methods)]); + $entityPostRoute = $this->createEntityPostRoute($name, $crudConfig, $action, + $controller); + $collection->add("chill_api_single_{$crudConfig['name']}_{$name}_create", + $entityPostRoute); + } - /** - * Load routes for api multi - * - * @param $crudConfig - * @return RouteCollection - */ - protected function loadApiMultiConfig(array $crudConfig): RouteCollection - { - $collection = new RouteCollection(); - $controller ='csapi_'.$crudConfig['name'].'_controller'; - - foreach ($crudConfig['actions'] as $name => $action) { - // filter only on single actions - $singleCollection = $action['single-collection'] ?? $name === '_index' ? 'collection' : NULL; - if ('single' === $singleCollection) { + if (count($methods) === 0) { + // the only method was POST, + // continue to next continue; } - $defaults = [ - '_controller' => $controller.':'.($action['controller_action'] ?? '_entity' === $name ? 'entityApi' : $name.'Api') - ]; - - // path are rewritten - // if name === 'default', we rewrite it to nothing :-) - $localName = '_entity' === $name ? '' : '/'.$name; - $localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}'; - $path = $crudConfig['base_path'].$localPath; - - $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ]; - - $methods = \array_keys(\array_filter($action['methods'], function($value, $key) { return $value; }, - ARRAY_FILTER_USE_BOTH)); - $route = new Route($path, $defaults, $requirements); $route->setMethods($methods); @@ -233,4 +211,18 @@ class CRUDRoutesLoader extends Loader return $collection; } + + private function createEntityPostRoute(string $name, $crudConfig, array $action, $controller): Route + { + $localPath = $action['path'].'.{_format}'; + $defaults = [ + '_controller' => $controller.':'.($action['controller_action'] ?? 'entityPost') + ]; + $path = $crudConfig['base_path'].$localPath; + $requirements = $action['requirements'] ?? []; + $route = new Route($path, $defaults, $requirements); + $route->setMethods([ Request::METHOD_POST ]); + + return $route; + } } diff --git a/src/Bundle/ChillMainBundle/Controller/SearchController.php b/src/Bundle/ChillMainBundle/Controller/SearchController.php index a44af8e7b..45390ceca 100644 --- a/src/Bundle/ChillMainBundle/Controller/SearchController.php +++ b/src/Bundle/ChillMainBundle/Controller/SearchController.php @@ -22,6 +22,7 @@ namespace Chill\MainBundle\Controller; +use Chill\MainBundle\Serializer\Model\Collection; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Chill\MainBundle\Search\UnknowSearchDomainException; @@ -34,6 +35,7 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Chill\MainBundle\Search\SearchProvider; use Symfony\Contracts\Translation\TranslatorInterface; use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\MainBundle\Search\SearchApi; /** * Class SearchController @@ -42,32 +44,24 @@ use Chill\MainBundle\Pagination\PaginatorFactory; */ class SearchController extends AbstractController { - /** - * - * @var SearchProvider - */ - protected $searchProvider; + protected SearchProvider $searchProvider; - /** - * - * @var TranslatorInterface - */ - protected $translator; + protected TranslatorInterface $translator; - /** - * - * @var PaginatorFactory - */ - protected $paginatorFactory; + protected PaginatorFactory $paginatorFactory; + + protected SearchApi $searchApi; function __construct( SearchProvider $searchProvider, TranslatorInterface $translator, - PaginatorFactory $paginatorFactory + PaginatorFactory $paginatorFactory, + SearchApi $searchApi ) { $this->searchProvider = $searchProvider; $this->translator = $translator; $this->paginatorFactory = $paginatorFactory; + $this->searchApi = $searchApi; } @@ -152,6 +146,19 @@ class SearchController extends AbstractController array('results' => $results, 'pattern' => $pattern) ); } + + public function searchApi(Request $request, $_format): JsonResponse + { + //TODO this is an incomplete implementation + $query = $request->query->get('q', ''); + + $results = $this->searchApi->getResults($query, 0, 150); + $paginator = $this->paginatorFactory->create(count($results)); + + $collection = new Collection($results, $paginator); + + return $this->json($collection); + } public function advancedSearchListAction(Request $request) { diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php index 1da1af2f3..a3c01fdf6 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php @@ -55,11 +55,11 @@ class LoadCenters extends AbstractFixture implements OrderedFixtureInterface public function load(ObjectManager $manager) { foreach (static::$centers as $new) { - $centerA = new Center(); - $centerA->setName($new['name']); + $center = new Center(); + $center->setName($new['name']); - $manager->persist($centerA); - $this->addReference($new['ref'], $centerA); + $manager->persist($center); + $this->addReference($new['ref'], $center); static::$refs[] = $new['ref']; } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index a40221263..5644138e6 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -35,6 +35,9 @@ use Chill\MainBundle\Doctrine\DQL\OverlapsI; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Chill\MainBundle\Doctrine\DQL\Replace; +use Chill\MainBundle\Doctrine\Type\NativeDateIntervalType; +use Chill\MainBundle\Doctrine\Type\PointType; +use Symfony\Component\HttpFoundation\Request; /** * Class ChillMainExtension @@ -133,7 +136,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $loader->load('services/search.yaml'); $loader->load('services/serializer.yaml'); - $this->configureCruds($container, $config['cruds'], $config['apis'], $loader); + $this->configureCruds($container, $config['cruds'], $config['apis'], $loader); } /** @@ -166,37 +169,49 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $container->prependExtensionConfig('twig', $twigConfig); //add DQL function to ORM (default entity_manager) - $container->prependExtensionConfig('doctrine', array( - 'orm' => array( - 'dql' => array( - 'string_functions' => array( - 'unaccent' => Unaccent::class, - 'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class, - 'AGGREGATE' => JsonAggregate::class, - 'REPLACE' => Replace::class, - ), - 'numeric_functions' => [ - 'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class, - 'SIMILARITY' => Similarity::class, - 'OVERLAPSI' => OverlapsI::class - ] - ) - ) - )); + $container + ->prependExtensionConfig( + 'doctrine', + [ + 'orm' => [ + 'dql' => [ + 'string_functions' => [ + 'unaccent' => Unaccent::class, + 'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class, + 'AGGREGATE' => JsonAggregate::class, + 'REPLACE' => Replace::class, + ], + 'numeric_functions' => [ + 'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class, + 'SIMILARITY' => Similarity::class, + 'OVERLAPSI' => OverlapsI::class, + ], + ], + ], + ], + ); //add dbal types (default entity_manager) - $container->prependExtensionConfig('doctrine', array( - 'dbal' => [ - 'types' => [ - 'dateinterval' => [ - 'class' => \Chill\MainBundle\Doctrine\Type\NativeDateIntervalType::class - ], - 'point' => [ - 'class' => \Chill\MainBundle\Doctrine\Type\PointType::class - ] - ] - ] - )); + $container + ->prependExtensionConfig( + 'doctrine', + [ + 'dbal' => [ + // This is mandatory since we are using postgis as database. + 'mapping_types' => [ + 'geometry' => 'string', + ], + 'types' => [ + 'dateinterval' => [ + 'class' => NativeDateIntervalType::class + ], + 'point' => [ + 'class' => PointType::class + ] + ] + ] + ] + ); //add current route to chill main $container->prependExtensionConfig('chill_main', array( @@ -212,6 +227,9 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $container->prependExtensionConfig('monolog', array( 'channels' => array('chill') )); + + //add crud api + $this->prependCruds($container); } /** @@ -235,4 +253,97 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, // Note: the controller are loaded inside compiler pass } + + + /** + * @param ContainerBuilder $container + */ + protected function prependCruds(ContainerBuilder $container) + { + $container->prependExtensionConfig('chill_main', [ + 'apis' => [ + [ + 'class' => \Chill\MainBundle\Entity\Address::class, + 'name' => 'address', + 'base_path' => '/api/1.0/main/address', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_POST => true, + Request::METHOD_HEAD => true + ] + ], + ] + ], + [ + 'class' => \Chill\MainBundle\Entity\AddressReference::class, + 'name' => 'address_reference', + 'base_path' => '/api/1.0/main/address-reference', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ] + ], + ] + ], + [ + 'class' => \Chill\MainBundle\Entity\PostalCode::class, + 'name' => 'postal_code', + 'base_path' => '/api/1.0/main/postal-code', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ] + ], + ] + ], + [ + 'class' => \Chill\MainBundle\Entity\Country::class, + 'name' => 'country', + 'base_path' => '/api/1.0/main/country', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ] + ], + ] + ] + ] + ]); + } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php index c7e4c00ef..4c90aaabb 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php @@ -221,6 +221,7 @@ class Configuration implements ConfigurationInterface ->booleanNode(Request::METHOD_POST)->defaultFalse()->end() ->booleanNode(Request::METHOD_DELETE)->defaultFalse()->end() ->booleanNode(Request::METHOD_PUT)->defaultFalse()->end() + ->booleanNode(Request::METHOD_PATCH)->defaultFalse()->end() ->end() ->end() ->arrayNode('roles') @@ -232,6 +233,7 @@ class Configuration implements ConfigurationInterface ->scalarNode(Request::METHOD_POST)->defaultNull()->end() ->scalarNode(Request::METHOD_DELETE)->defaultNull()->end() ->scalarNode(Request::METHOD_PUT)->defaultNull()->end() + ->scalarNode(Request::METHOD_PATCH)->defaultNull()->end() ->end() ->end() ->end() diff --git a/src/Bundle/ChillMainBundle/Doctrine/Event/TrackCreateUpdateSubscriber.php b/src/Bundle/ChillMainBundle/Doctrine/Event/TrackCreateUpdateSubscriber.php new file mode 100644 index 000000000..3926e2062 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Doctrine/Event/TrackCreateUpdateSubscriber.php @@ -0,0 +1,65 @@ +security = $security; + } + + /** + * {@inheritDoc} + */ + public function getSubscribedEvents() + { + return [ + Events::prePersist, + Events::preUpdate + ]; + } + + public function prePersist(LifecycleEventArgs $args): void + { + $object = $args->getObject(); + + if ($object instanceof TrackCreationInterface + && $this->security->getUser() instanceof User) { + $object->setCreatedBy($this->security->getUser()); + $object->setCreatedAt(new \DateTimeImmutable('now')); + } + + $this->onUpdate($object); + } + + public function preUpdate(LifecycleEventArgs $args): void + { + $object = $args->getObject(); + + $this->onUpdate($object); + } + + protected function onUpdate(object $object): void + { + if ($object instanceof TrackUpdateInterface + && $this->security->getUser() instanceof User) { + $object->setUpdatedBy($this->security->getUser()); + $object->setUpdatedAt(new \DateTimeImmutable('now')); + } + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/Model/TrackCreationInterface.php b/src/Bundle/ChillMainBundle/Doctrine/Model/TrackCreationInterface.php new file mode 100644 index 000000000..192d4e7a9 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Doctrine/Model/TrackCreationInterface.php @@ -0,0 +1,12 @@ + + * @DiscriminatorMap(typeProperty="type", mapping={ + * "scope"=Scope::class + * }) */ class Scope { @@ -40,6 +43,7 @@ class Scope * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") + * @Groups({"read"}) */ private $id; @@ -49,6 +53,7 @@ class Scope * @var array * * @ORM\Column(type="json_array") + * @Groups({"read"}) */ private $name = []; diff --git a/src/Bundle/ChillMainBundle/Entity/User.php b/src/Bundle/ChillMainBundle/Entity/User.php index 1a46b30a5..11d168f84 100644 --- a/src/Bundle/ChillMainBundle/Entity/User.php +++ b/src/Bundle/ChillMainBundle/Entity/User.php @@ -7,6 +7,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; use Symfony\Component\Security\Core\User\AdvancedUserInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; /** * User @@ -14,6 +15,9 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * @ORM\Entity(repositoryClass="Chill\MainBundle\Repository\UserRepository") * @ORM\Table(name="users") * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region") + * @DiscriminatorMap(typeProperty="type", mapping={ + * "user"=User::class + * }) */ class User implements AdvancedUserInterface { diff --git a/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss b/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss index 4d3b86ed3..8a77ac48f 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss @@ -28,7 +28,7 @@ // @import "bootstrap/scss/card"; // @import "bootstrap/scss/breadcrumb"; // @import "bootstrap/scss/pagination"; -// @import "bootstrap/scss/badge"; +@import "bootstrap/scss/badge"; // @import "bootstrap/scss/jumbotron"; // @import "bootstrap/scss/alert"; // @import "bootstrap/scss/progress"; @@ -41,7 +41,7 @@ // @import "bootstrap/scss/popover"; // @import "bootstrap/scss/carousel"; // @import "bootstrap/scss/spinners"; -// @import "bootstrap/scss/utilities"; +@import "bootstrap/scss/utilities"; // @import "bootstrap/scss/print"; @import "custom"; diff --git a/src/Bundle/ChillMainBundle/Resources/public/modules/scratch/custom/modules/_buttons.scss b/src/Bundle/ChillMainBundle/Resources/public/modules/scratch/custom/modules/_buttons.scss index e94aa494e..0b7af263b 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/modules/scratch/custom/modules/_buttons.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/modules/scratch/custom/modules/_buttons.scss @@ -5,7 +5,7 @@ @include button($green, $white); } - &.bt-reset, &.bt-delete { + &.bt-reset, &.bt-delete, &.bt-remove { @include button($red, $white); } @@ -24,6 +24,7 @@ &.bt-save::before, &.bt-new::before, &.bt-delete::before, + &.bt-remove::before, &.bt-update::before, &.bt-edit::before, &.bt-cancel::before, @@ -56,7 +57,12 @@ // add a trash content: ""; } - + + &.bt-remove::before { + // add a times + content: ""; + } + &.bt-edit::before, &.bt-update::before { // add a pencil content: ""; @@ -94,6 +100,7 @@ &.bt-save::before, &.bt-new::before, &.bt-delete::before, + &.bt-remove::before, &.bt-update::before, &.bt-edit::before, &.bt-cancel::before, @@ -123,6 +130,7 @@ &.bt-save::before, &.bt-new::before, &.bt-delete::before, + &.bt-remove::before, &.bt-update::before, &.bt-edit::before, &.bt-cancel::before, diff --git a/src/Bundle/ChillMainBundle/Resources/public/scss/chillmain.scss b/src/Bundle/ChillMainBundle/Resources/public/scss/chillmain.scss index 4f9ecf46d..6aa0e1c8d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/scss/chillmain.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/scss/chillmain.scss @@ -13,9 +13,9 @@ div#header-accompanying_course-name { background: none repeat scroll 0 0 #718596; color: #FFF; - padding-top: 1em; - padding-bottom: 1em; - + h1 { + margin: 0.4em 0; + } span { a { color: white; @@ -39,19 +39,25 @@ div.subheader { height: 130px; } -div.vue-component { - padding: 1.5em; - margin: 2em 0; - border: 2px dashed grey; - position: relative; - &:before { - content: "vuejs component"; - position: absolute; - left: 1.5em; - top: -0.9em; - background-color: white; - color: grey; - padding: 0 0.3em; - } - dd { margin-left: 1em; } +//// SCRATCH BUTTONS +.sc-button { + &.disabled { + cursor: default; + &.bt-remove { + background-color: #d9d9d9; + } + } +} + +//// à ranger +.discret { + color: grey; + margin-right: 1em; +} + +table { + ul.record_actions { + margin: 0; + padding: 0.5em; + } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue new file mode 100644 index 000000000..461b0038e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/index.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/index.js new file mode 100644 index 000000000..27676369e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/index.js @@ -0,0 +1,16 @@ +import { createApp } from 'vue' +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' +import { addressMessages } from './js/i18n' +import { store } from './store' + +import App from './App.vue'; + +const i18n = _createI18n(addressMessages); + +const app = createApp({ + template: ``, +}) +.use(store) +.use(i18n) +.component('app', App) +.mount('#address'); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/js/i18n.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/js/i18n.js new file mode 100644 index 000000000..87ee9a5c0 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/js/i18n.js @@ -0,0 +1,22 @@ +const addressMessages = { + fr: { + add_an_address: 'Ajouter une adresse', + select_an_address: 'Sélectionner une adresse', + fill_an_address: 'Compléter l\'adresse', + select_country: 'Choisir le pays', + select_city: 'Choisir une localité', + select_address: 'Choisir une adresse', + isNoAddress: 'L\'adresse n\'est pas celle d\'un domicile fixe ?', + floor: 'Étage', + corridor: 'Couloir', + steps: 'Escalier', + flat: 'Appartement', + buildingName: 'Nom du batiment', + extra: 'Complément d\'adresse', + distribution: 'Service particulier de distribution' + } +}; + +export { + addressMessages +}; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/store/index.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/store/index.js new file mode 100644 index 000000000..b9466b50c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/store/index.js @@ -0,0 +1,43 @@ +import 'es6-promise/auto'; +import { createStore } from 'vuex'; + +// le fetch POST serait rangé dans la logique du composant qui appelle AddAddress +//import { postAddress } from '... api' + +const debug = process.env.NODE_ENV !== 'production'; + +const store = createStore({ + strict: debug, + state: { + address: {}, + errorMsg: {} + }, + getters: { + }, + mutations: { + addAddress(state, address) { + console.log('@M addAddress address', address); + state.address = address; + } + }, + actions: { + addAddress({ commit }, payload) { + console.log('@A addAddress payload', payload); + commit('addAddress', payload); // à remplacer par + + // fetch POST qui envoie l'adresse, et récupère la confirmation que c'est ok. + // La confirmation est l'adresse elle-même. + // + // postAddress(payload) + // .fetch(address => new Promise((resolve, reject) => { + // commit('addAddress', address); + // resolve(); + // })) + // .catch((error) => { + // state.errorMsg.push(error.message); + // }); + } + } +}); + +export { store }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_api/AddAddress.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_api/AddAddress.js new file mode 100644 index 000000000..4de0fcc19 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_api/AddAddress.js @@ -0,0 +1,46 @@ +/* +* Endpoint countries GET +* TODO +*/ +const fetchCountries = () => { + console.log('<<< fetching countries'); + return [ + {id: 1, name: 'France', countryCode: 'FR'}, + {id: 2, name: 'Belgium', countryCode: 'BE'} + ]; +}; + +/* +* Endpoint cities GET +* TODO +*/ +const fetchCities = (country) => { + console.log('<<< fetching cities for', country); + return [ + {id: 1, name: 'Bruxelles', code: '1000', country: 'BE'}, + {id: 2, name: 'Aisne', code: '85045', country: 'FR'}, + {id: 3, name: 'Saint-Gervais', code: '85230', country: 'FR'} + ]; +}; + +/* +* Endpoint chill_main_address_reference_api_show +* method GET, get AddressReference Object +* @returns {Promise} a promise containing all AddressReference object +*/ +const fetchReferenceAddresses = (city) => { + console.log('<<< fetching references addresses for', city); // city n'est pas utilisé pour le moment + + const url = `/api/1.0/main/address-reference.json`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +export { + fetchCountries, + fetchCities, + fetchReferenceAddresses +}; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue new file mode 100644 index 000000000..55cf2f098 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue @@ -0,0 +1,219 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMap.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMap.vue new file mode 100644 index 000000000..5b616819d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMap.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMore.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMore.vue new file mode 100644 index 000000000..b216d5ed0 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMore.vue @@ -0,0 +1,112 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressSelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressSelection.vue new file mode 100644 index 000000000..71738079c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressSelection.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CitySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CitySelection.vue new file mode 100644 index 000000000..99e46db08 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CitySelection.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue new file mode 100644 index 000000000..4eb591135 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue new file mode 100644 index 000000000..7498d66f3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js new file mode 100644 index 000000000..319378d7e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js @@ -0,0 +1,63 @@ +import { createI18n } from 'vue-i18n' + +const datetimeFormats = { + fr: { + short: { + year: "numeric", + month: "numeric", + day: "numeric" + }, + text: { + year: "numeric", + month: "long", + day: "numeric", + }, + long: { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + hour12: false + } + } +}; +const messages = { + fr: { + action: { + actions: "Actions", + show: "Voir", + edit: "Modifier", + create: "Créer", + remove: "Enlever", + delete: "Supprimer", + save: "Enregistrer", + add: "Ajouter", + show_modal: "Ouvrir une modale", + ok: "OK", + cancel: "Annuler", + close: "Fermer", + back: "Retour", + check_all: "cocher tout", + reset: "réinitialiser" + }, + nav: { + next: "Suivant", + previous: "Précédent", + top: "Haut", + bottom: "Bas", + } + } +}; + +const _createI18n = (appMessages) => { + Object.assign(messages.fr, appMessages.fr); + return createI18n({ + locale: 'fr', + fallbackLocale: 'fr', + datetimeFormats, + messages, + }) +}; + +export { _createI18n } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Layout/_footer.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Layout/_footer.html.twig index 132e81a3e..0d23d499d 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Layout/_footer.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Layout/_footer.html.twig @@ -1,4 +1,4 @@

{{ 'This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License'|trans|raw }} -
{{ 'User manual'|trans }}

-
\ No newline at end of file +
{{ 'User manual'|trans }}

+ diff --git a/src/Bundle/ChillMainBundle/Resources/views/Layout/_header-logo.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Layout/_header-logo.html.twig index be59b454f..f619dc151 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Layout/_header-logo.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Layout/_header-logo.html.twig @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/Bundle/ChillMainBundle/Search/Model/Result.php b/src/Bundle/ChillMainBundle/Search/Model/Result.php new file mode 100644 index 000000000..a88c2f55f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Search/Model/Result.php @@ -0,0 +1,36 @@ +relevance = $relevance; + $this->result = $result; + } + + public function getRelevance(): float + { + return $this->relevance; + } + + public function getResult() + { + return $this->result; + } + + + +} diff --git a/src/Bundle/ChillMainBundle/Search/SearchApi.php b/src/Bundle/ChillMainBundle/Search/SearchApi.php new file mode 100644 index 000000000..518edd31b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Search/SearchApi.php @@ -0,0 +1,89 @@ +em = $em; + $this->search = $search; + } + + /** + * @return Model/Result[] + */ + public function getResults(string $query, int $offset, int $maxResult): array + { + // **warning again**: this is an incomplete implementation + $results = []; + + foreach ($this->getPersons($query) as $p) { + $results[] = new Model\Result((float)\rand(0, 100) / 100, $p); + } + foreach ($this->getThirdParties($query) as $t) { + $results[] = new Model\Result((float)\rand(0, 100) / 100, $t); + } + + \usort($results, function(Model\Result $a, Model\Result $b) { + return ($a->getRelevance() <=> $b->getRelevance()) * -1; + }); + + return $results; + } + + public function countResults(string $query): int + { + return 0; + } + + private function getThirdParties(string $query) + { + $thirdPartiesIds = $this->em->createQuery('SELECT t.id FROM '.ThirdParty::class.' t') + ->getScalarResult(); + $nbResults = rand(0, 15); + + if ($nbResults === 1) { + $nbResults++; + } elseif ($nbResults === 0) { + return []; + } + $ids = \array_map(function ($e) use ($thirdPartiesIds) { return $thirdPartiesIds[$e]['id'];}, + \array_rand($thirdPartiesIds, $nbResults)); + + $a = $this->em->getRepository(ThirdParty::class) + ->findById($ids); + return $a; + } + + private function getPersons(string $query) + { + $params = [ + SearchInterface::SEARCH_PREVIEW_OPTION => false + ]; + $search = $this->search->getResultByName($query, 'person_regular', 0, 50, $params, 'json'); + $ids = \array_map(function($r) { return $r['id']; }, $search['results']); + + + if (count($ids) === 0) { + return []; + } + + return $this->em->getRepository(Person::class) + ->findById($ids) + ; + } + +} diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php new file mode 100644 index 000000000..29d760b71 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php @@ -0,0 +1,29 @@ +getId(); + $data['text'] = $address->getStreet().', '.$address->getBuildingName(); + $data['postcode']['name'] = $address->getPostCode()->getName(); + + return $data; + } + + public function supportsNormalization($data, string $format = null) + { + return $data instanceof Address; + } + + +} diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php index 26182d936..f1681c60a 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php @@ -20,19 +20,32 @@ namespace Chill\MainBundle\Serializer\Normalizer; use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Repository\CenterRepository; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Exception\UnexpectedValueException; /** * * */ -class CenterNormalizer implements NormalizerInterface +class CenterNormalizer implements NormalizerInterface, DenormalizerInterface { + private CenterRepository $repository; + + + public function __construct(CenterRepository $repository) + { + $this->repository = $repository; + } + public function normalize($center, string $format = null, array $context = array()) { /** @var Center $center */ return [ 'id' => $center->getId(), + 'type' => 'center', 'name' => $center->getName() ]; } @@ -41,4 +54,30 @@ class CenterNormalizer implements NormalizerInterface { return $data instanceof Center; } + + public function denormalize($data, string $type, string $format = null, array $context = []) + { + if (FALSE === \array_key_exists('type', $data)) { + throw new InvalidArgumentException('missing "type" key in data'); + } + if ('center' !== $data['type']) { + throw new InvalidArgumentException('type should be equal to "center"'); + } + if (FALSE === \array_key_exists('id', $data)) { + throw new InvalidArgumentException('missing "id" key in data'); + } + + $center = $this->repository->find($data['id']); + + if (null === $center) { + throw new UnexpectedValueException("The type with id {$data['id']} does not exists"); + } + + return $center; + } + + public function supportsDenormalization($data, string $type, string $format = null) + { + return $type === Center::class; + } } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php index fd902b184..8e333a2a4 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php @@ -20,15 +20,16 @@ namespace Chill\MainBundle\Serializer\Normalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; -class DateNormalizer implements NormalizerInterface +class DateNormalizer implements NormalizerInterface, DenormalizerInterface { public function normalize($date, string $format = null, array $context = array()) { /** @var \DateTimeInterface $date */ return [ - 'datetime' => $date->format(\DateTimeInterface::ISO8601), - 'u' => $date->getTimestamp() + 'datetime' => $date->format(\DateTimeInterface::ISO8601) ]; } @@ -36,4 +37,24 @@ class DateNormalizer implements NormalizerInterface { return $data instanceof \DateTimeInterface; } + + public function denormalize($data, string $type, string $format = null, array $context = []) + { + switch ($type) { + case \DateTime::class: + return \DateTime::createFromFormat(\DateTimeInterface::ISO8601, $data['datetime']); + case \DateTimeInterface::class: + case \DateTimeImmutable::class: + default: + return \DateTimeImmutable::createFromFormat(\DateTimeInterface::ISO8601, $data['datetime']); + } + } + + public function supportsDenormalization($data, string $type, string $format = null): bool + { + return $type === \DateTimeInterface::class || + $type === \DateTime::class || + $type === \DateTimeImmutable::class || + (\is_array($data) && array_key_exists('datetime', $data)); + } } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php new file mode 100644 index 000000000..25b2c5017 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php @@ -0,0 +1,71 @@ +denormalizer->supportsDenormalization($data, $localType, $format)) { + try { + return $this->denormalizer->denormalize($data, $localType, $format, $context); } catch (RuntimeException $e) { + $lastException = $e; + } + } + } + + throw new RuntimeException(sprintf("Could not find any denormalizer for those ". + "ALLOWED_TYPES: %s", \implode(", ", $context[self::ALLOWED_TYPES]))); + } + + /** + * {@inheritDoc} + */ + public function supportsDenormalization($data, string $type, string $format = null, array $context = []) + { + if (self::TYPE !== $type) { + return false; + } + + if (0 === count($context[self::ALLOWED_TYPES] ?? [])) { + throw new \LogicException("The context should contains a list of + allowed types"); + } + + foreach ($context[self::ALLOWED_TYPES] as $localType) { + if ($this->denormalizer->supportsDenormalization($data, $localType, $format)) { + return true; + } + } + + return false; + } + +} diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php new file mode 100644 index 000000000..cb2635802 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php @@ -0,0 +1,71 @@ +em = $em; + $this->serializerMetadataFactory = $serializerMetadataFactory; + } + + public function denormalize($data, string $type, string $format = null, array $context = []) + { + if (\array_key_exists(AbstractNormalizer::OBJECT_TO_POPULATE, $context)) { + return $context[AbstractNormalizer::OBJECT_TO_POPULATE]; + } + + return $this->em->getRepository($type) + ->find($data['id']); + } + + public function supportsDenormalization($data, string $type, string $format = null) + { + if (FALSE === \is_array($data)) { + return false; + } + + if (FALSE === \array_key_exists('id', $data)) { + return false; + } + + if (FALSE === $this->em->getClassMetadata($type) instanceof ClassMetadata) { + return false; + } + + // does have serializer metadata, and class discriminator ? + if ($this->serializerMetadataFactory->hasMetadataFor($type)) { + + $classDiscriminator = $this->serializerMetadataFactory + ->getMetadataFor($type)->getClassDiscriminatorMapping(); + + if ($classDiscriminator) { + $typeProperty = $classDiscriminator->getTypeProperty(); + + // check that only 2 keys + // that the second key is property + // and that the type match the class for given type property + return count($data) === 2 + && \array_key_exists($typeProperty, $data) + && $type === $classDiscriminator->getClassForType($data[$typeProperty]); + } + } + + // we do not have any class discriminator. Check that the id is the only one key + return count($data) === 1; + } +} diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php index 2a71de52b..4c1dc1523 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php @@ -24,7 +24,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * - * + * @internal we keep this normalizer, because the property 'text' may be replace by a rendering in the future */ class UserNormalizer implements NormalizerInterface { @@ -32,8 +32,10 @@ class UserNormalizer implements NormalizerInterface { /** @var User $user */ return [ + 'type' => 'user', 'id' => $user->getId(), - 'username' => $user->getUsername() + 'username' => $user->getUsername(), + 'text' => $user->getUsername() ]; } diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php index d258c0d30..e480ab145 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php @@ -172,6 +172,7 @@ abstract class AbstractExportTest extends WebTestCase */ public function testInitiateQuery($modifiers, $acl, $data) { + var_dump($data); $query = $this->getExport()->initiateQuery($modifiers, $acl, $data); $this->assertTrue($query instanceof QueryBuilder || $query instanceof NativeQuery, diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php new file mode 100644 index 000000000..c4f4bc13d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php @@ -0,0 +1,51 @@ +get(EntityManagerInterface::class); + $serializerFactory = self::$container->get(ClassMetadataFactoryInterface::class); + + $this->normalizer = new DoctrineExistingEntityNormalizer($em, $serializerFactory); + } + + /** + * @dataProvider dataProviderUserId + */ + public function testGetMappedClass($userId) + { + $data = [ 'type' => 'user', 'id' => $userId]; + $supports = $this->normalizer->supportsDenormalization($data, User::class); + + $this->assertTrue($supports); + } + + public function dataProviderUserId() + { + self::bootKernel(); + + $userIds = self::$container->get(EntityManagerInterface::class) + ->getRepository(User::class) + ->createQueryBuilder('u') + ->select('u.id') + ->setMaxResults(1) + ->getQuery() + ->getResult() + ; + + yield [ $userIds[0]['id'] ]; + } +} diff --git a/src/Bundle/ChillMainBundle/chill.api.specs.yaml b/src/Bundle/ChillMainBundle/chill.api.specs.yaml new file mode 100644 index 000000000..68a3eb764 --- /dev/null +++ b/src/Bundle/ChillMainBundle/chill.api.specs.yaml @@ -0,0 +1,58 @@ +--- +openapi: "3.0.0" +info: + version: "1.0.0" + title: "Chill api" + description: "Api documentation for chill. Currently, work in progress" +servers: + - url: "/api" + description: "Your current dev server" + +components: + schemas: + Center: + type: object + properties: + id: + type: integer + name: + type: string + +paths: + /1.0/search.json: + get: + summary: perform a search across multiple entities + tags: + - search + - person + - thirdparty + description: > + **Warning**: This is currently a stub (not really implemented + + The search is performed across multiple entities. The entities must be listed into + `type` parameters. + + The results are ordered by relevance, from the most to the lowest relevant. + + parameters: + - name: q + in: query + required: true + description: the pattern to search + schema: + type: string + - name: type[] + in: query + required: true + description: the type entities amongst the search is performed + schema: + type: array + items: + type: string + enum: + - person + - thirdparty + responses: + 200: + description: "OK" + diff --git a/src/Bundle/ChillMainBundle/chill.webpack.config.js b/src/Bundle/ChillMainBundle/chill.webpack.config.js index 6187b31ea..78accf004 100644 --- a/src/Bundle/ChillMainBundle/chill.webpack.config.js +++ b/src/Bundle/ChillMainBundle/chill.webpack.config.js @@ -62,5 +62,7 @@ module.exports = function(encore, entries) buildCKEditor(encore); encore.addEntry('ckeditor5', __dirname + '/Resources/public/modules/ckeditor5/index.js'); + // Address + encore.addEntry('address', __dirname + '/Resources/public/vuejs/Address/index.js'); }; diff --git a/src/Bundle/ChillMainBundle/config/routes.yaml b/src/Bundle/ChillMainBundle/config/routes.yaml index 3fd7eafab..bc3187add 100644 --- a/src/Bundle/ChillMainBundle/config/routes.yaml +++ b/src/Bundle/ChillMainBundle/config/routes.yaml @@ -69,6 +69,13 @@ chill_main_search: requirements: _format: html|json +chill_main_search_global: + path: '/api/1.0/search.{_format}' + controller: Chill\MainBundle\Controller\SearchController::searchApi + format: 'json' + requirements: + _format: 'json' + chill_main_advanced_search: path: /{_locale}/search/advanced/{name} controller: Chill\MainBundle\Controller\SearchController::advancedSearchAction diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index 93a4ee4e4..a6afa4336 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -3,6 +3,18 @@ parameters: services: + Chill\MainBundle\Serializer\Normalizer\: + resource: '../Serializer/Normalizer' + autowire: true + tags: + - { name: 'serializer.normalizer', priority: 64 } + + Chill\MainBundle\Doctrine\Event\: + resource: '../Doctrine/Event/' + autowire: true + tags: + - { name: 'doctrine.event_subscriber' } + chill.main.helper.translatable_string: class: Chill\MainBundle\Templating\TranslatableStringHelper arguments: diff --git a/src/Bundle/ChillMainBundle/config/services/controller.yaml b/src/Bundle/ChillMainBundle/config/services/controller.yaml index 5fb542786..6021e3d72 100644 --- a/src/Bundle/ChillMainBundle/config/services/controller.yaml +++ b/src/Bundle/ChillMainBundle/config/services/controller.yaml @@ -16,6 +16,7 @@ services: $searchProvider: '@chill_main.search_provider' $translator: '@Symfony\Contracts\Translation\TranslatorInterface' $paginatorFactory: '@Chill\MainBundle\Pagination\PaginatorFactory' + $searchApi: '@Chill\MainBundle\Search\SearchApi' tags: ['controller.service_arguments'] Chill\MainBundle\Controller\PermissionsGroupController: diff --git a/src/Bundle/ChillMainBundle/config/services/search.yaml b/src/Bundle/ChillMainBundle/config/services/search.yaml index e8a457415..b7a1656b3 100644 --- a/src/Bundle/ChillMainBundle/config/services/search.yaml +++ b/src/Bundle/ChillMainBundle/config/services/search.yaml @@ -1,3 +1,10 @@ services: chill_main.search_provider: - class: Chill\MainBundle\Search\SearchProvider \ No newline at end of file + class: Chill\MainBundle\Search\SearchProvider + + Chill\MainBundle\Search\SearchProvider: '@chill_main.search_provider' + + Chill\MainBundle\Search\SearchApi: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + $search: '@Chill\MainBundle\Search\SearchProvider' diff --git a/src/Bundle/ChillMainBundle/config/services/serializer.yaml b/src/Bundle/ChillMainBundle/config/services/serializer.yaml index fb5f57b7e..c7cc6ca63 100644 --- a/src/Bundle/ChillMainBundle/config/services/serializer.yaml +++ b/src/Bundle/ChillMainBundle/config/services/serializer.yaml @@ -1,17 +1,11 @@ --- services: - Chill\MainBundle\Serializer\Normalizer\CenterNormalizer: - tags: - - { name: 'serializer.normalizer', priority: 64 } + + # note: the autowiring for serializers and normalizers is declared + # into ../services.yaml - Chill\MainBundle\Serializer\Normalizer\DateNormalizer: + Chill\MainBundle\Serializer\Normalizer\DoctrineExistingEntityNormalizer: + autowire: true tags: - - { name: 'serializer.normalizer', priority: 64 } + - { name: 'serializer.normalizer', priority: 8 } - Chill\MainBundle\Serializer\Normalizer\UserNormalizer: - tags: - - { name: 'serializer.normalizer', priority: 64 } - - Chill\MainBundle\Serializer\Normalizer\CollectionNormalizer: - tags: - - { name: 'serializer.normalizer', priority: 64 } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210525144016.php b/src/Bundle/ChillMainBundle/migrations/Version20210525144016.php new file mode 100644 index 000000000..3be8d9ea1 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20210525144016.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE chill_main_address DROP CONSTRAINT FK_165051F6114B8DD9'); + $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT FK_165051F6114B8DD9 FOREIGN KEY (linkedToThirdParty_id) REFERENCES chill_3party.third_party (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_address DROP CONSTRAINT fk_165051f6114b8dd9'); + $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT fk_165051f6114b8dd9 FOREIGN KEY (linkedtothirdparty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php index dc666400f..860cdfad7 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php @@ -5,13 +5,19 @@ namespace Chill\PersonBundle\Controller; use Chill\MainBundle\CRUD\Controller\ApiController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent; use Chill\PersonBundle\Entity\Person; +use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Symfony\Component\Serializer\Exception\RuntimeException; +use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; +use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; +use Chill\PersonBundle\Entity\SocialWork\SocialIssue; +use Chill\MainBundle\Entity\Scope; +use Symfony\Component\Workflow\Registry; class AccompanyingCourseApiController extends ApiController { @@ -19,10 +25,37 @@ class AccompanyingCourseApiController extends ApiController protected ValidatorInterface $validator; - public function __construct(EventDispatcherInterface $eventDispatcher, $validator) - { + private Registry $registry; + + public function __construct( + EventDispatcherInterface $eventDispatcher, + ValidatorInterface $validator, + Registry $registry + ) { $this->eventDispatcher = $eventDispatcher; $this->validator = $validator; + $this->registry = $registry; + } + + public function confirmApi($id, Request $request, $_format): Response + { + /** @var AccompanyingPeriod $accompanyingPeriod */ + $accompanyingPeriod = $this->getEntity('participation', $id, $request); + + $this->checkACL('confirm', $request, $_format, $accompanyingPeriod); +$workflow = $this->registry->get($accompanyingPeriod); + + if (FALSE === $workflow->can($accompanyingPeriod, 'confirm')) { + throw new BadRequestException('It is not possible to confirm this period'); + } + + $workflow->apply($accompanyingPeriod, 'confirm'); + + $this->getDoctrine()->getManager()->flush(); + + return $this->json($accompanyingPeriod, Response::HTTP_OK, [], [ + 'groups' => [ 'read' ] + ]); } public function participationApi($id, Request $request, $_format) @@ -55,12 +88,76 @@ class AccompanyingCourseApiController extends ApiController if ($errors->count() > 0) { // only format accepted - return $this->json($errors); + return $this->json($errors, 422); } $this->getDoctrine()->getManager()->flush(); - return $this->json($participation); + return $this->json($participation, 200, [], ['groups' => [ 'read' ]]); + } + + public function resourceApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('resource', $id, $request, $_format, 'resource', Resource::class); + } + + public function scopeApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('scope', $id, $request, $_format, 'scope', Scope::class, [ 'groups' => [ 'read' ] ]); + } + + public function commentApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('comment', $id, $request, $_format, 'comment', Comment::class); + } + + public function socialIssueApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('socialissue', $id, $request, $_format, 'socialIssue', SocialIssue::class, [ 'groups' => [ 'read' ] ]); + } + + public function requestorApi($id, Request $request, string $_format): Response + { + /** @var AccompanyingPeriod $accompanyingPeriod */ + $action = 'requestor'; + $accompanyingPeriod = $this->getEntity($action, $id, $request); + // a requestor may be a person or a thirdParty + + $this->checkACL($action, $request, $_format, $accompanyingPeriod); + $this->onPostCheckACL($action, $request, $_format, $accompanyingPeriod); + + if (Request::METHOD_DELETE === $request->getMethod()) { + $accompanyingPeriod->setRequestor(NULL); + } elseif (Request::METHOD_POST === $request->getMethod()) { + $requestor = null; + $exceptions = []; + foreach ([Person::class, ThirdParty::class] as $class) { + try { + $requestor = $this->getSerializer() + ->deserialize($request->getContent(), $class, $_format, []); + } catch (RuntimeException $e) { + $exceptions[] = $e; + } + } + if ($requestor === null) { + throw new BadRequestException('Could not find any person or requestor', 0, $exceptions[0]); + } + + $accompanyingPeriod->setRequestor($requestor); + } else { + throw new BadRequestException('method not supported'); + } + + $errors = $this->validator->validate($accompanyingPeriod); + + if ($errors->count() > 0) { + // only format accepted + return $this->json($errors, 422); + } + + $this->getDoctrine()->getManager()->flush(); + + return $this->json($accompanyingPeriod->getRequestor(), 200, [], ['groups' => [ 'read']]); } protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php index 9bc732d87..6786cb05f 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php @@ -6,6 +6,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\JsonResponse; @@ -42,6 +43,41 @@ class AccompanyingCourseController extends Controller $this->dispatcher = $dispatcher; $this->validator = $validator; } + + /** + * @Route("/{_locale}/person/parcours/new", name="chill_person_accompanying_course_new") + */ + public function newAction(Request $request): Response + { + $period = new AccompanyingPeriod(); + $em = $this->getDoctrine()->getManager(); + + if ($request->query->has('person_id')) { + $personIds = $request->query->get('person_id'); + + if (FALSE === \is_array($personIds)) { + throw new BadRequestException("person_id parameter should be an array"); + } + + foreach ($personIds as $personId) { + $person = $em->getRepository(Person::class)->find($personId); + if (NULL !== $person) { + $period->addPerson($person); + } + } + } + + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $period); + + $em->persist($period); + $em->flush(); + + return $this->redirectToRoute('chill_person_accompanying_course_show', [ + 'accompanying_period_id' => $period->getId() + ]); + + } + /** * Homepage of Accompanying Course section * @@ -86,76 +122,4 @@ class AccompanyingCourseController extends Controller ]); } - /** - * Get API Data for showing endpoint - * - * @Route( - * "/{_locale}/person/api/1.0/accompanying-course/{accompanying_period_id}/show.{_format}", - * name="chill_person_accompanying_course_api_show" - * ) - * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) - */ - public function showAPI(AccompanyingPeriod $accompanyingCourse, $_format): Response - { - // TODO check ACL on AccompanyingPeriod - - $this->dispatcher->dispatch( - AccompanyingPeriodPrivacyEvent::ACCOMPANYING_PERIOD_PRIVACY_EVENT, - new AccompanyingPeriodPrivacyEvent($accompanyingCourse, [ - 'action' => 'showApi' - ]) - ); - - switch ($_format) { - case 'json': - return $this->json($accompanyingCourse); - default: - throw new BadRequestException('Unsupported format'); - } - - } - - /** - * Get API Data for showing endpoint - * - * @Route( - * "/{_locale}/person/api/1.0/accompanying-course/{accompanying_period_id}/participation.{_format}", - * name="chill_person_accompanying_course_api_add_participation", - * methods={"POST"}, - * format="json", - * requirements={ - * "_format": "json", - * } - * ) - * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) - */ - public function addParticipationAPI(Request $request, AccompanyingPeriod $accompanyingCourse, $_format): Response - { - switch ($_format) { - case 'json': - $person = $this->serializer->deserialize($request->getContent(), Person::class, $_format, [ - - ]); - break; - default: - throw new BadRequestException('Unsupported format'); - } - - if (NULL === $person) { - throw new BadRequestException('person id not found'); - } - - // TODO add acl - $accompanyingCourse->addPerson($person); - $errors = $this->validator->validate($accompanyingCourse); - - if ($errors->count() > 0) { - // only format accepted - return $this->json($errors); - } - - $this->getDoctrine()->getManager()->flush(); - - return new JsonResponse(); - } } diff --git a/src/Bundle/ChillPersonBundle/Controller/ApiPersonController.php b/src/Bundle/ChillPersonBundle/Controller/ApiPersonController.php deleted file mode 100644 index bfaf22d7b..000000000 --- a/src/Bundle/ChillPersonBundle/Controller/ApiPersonController.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -namespace Chill\PersonBundle\Controller; - -use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Component\HttpFoundation\JsonResponse; - - -class ApiPersonController extends Controller -{ - public function viewAction($id, $_format) - { - - } -} diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php b/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php new file mode 100644 index 000000000..84f1ebf66 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php @@ -0,0 +1,50 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\PersonBundle\Controller; + +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Role\Role; +use Chill\MainBundle\CRUD\Controller\ApiController; +use Symfony\Component\HttpFoundation\Request; + + +class PersonApiController extends ApiController +{ + private AuthorizationHelper $authorizationHelper; + + /** + * @param AuthorizationHelper $authorizationHelper + */ + public function __construct(AuthorizationHelper $authorizationHelper) + { + $this->authorizationHelper = $authorizationHelper; + } + + protected function createEntity(string $action, Request $request): object + { + $person = parent::createEntity($action, $request); + + // TODO temporary hack to allow creation of person with fake center + $centers = $this->authorizationHelper->getReachableCenters($this->getUser(), + new Role(PersonVoter::CREATE)); + $person->setCenter($centers[0]); + + return $person; + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonController.php b/src/Bundle/ChillPersonBundle/Controller/PersonController.php index bfb32654e..02320aed3 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonController.php @@ -38,6 +38,7 @@ use Symfony\Component\Translation\TranslatorInterface; use Chill\MainBundle\Search\SearchProvider; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; +use Chill\PersonBundle\Repository\PersonNotDuplicateRepository; use Symfony\Component\Validator\Validator\ValidatorInterface; use Doctrine\ORM\EntityManagerInterface; @@ -290,7 +291,7 @@ final class PersonController extends AbstractController return $errors; } - public function reviewAction(Request $request) + public function reviewAction(Request $request, PersonNotDuplicateRepository $personNotDuplicateRepository) { if ($request->getMethod() !== 'POST') { $r = new Response("You must send something to review the creation of a new Person"); @@ -299,7 +300,6 @@ final class PersonController extends AbstractController } $form = $this->createForm( - //CreationPersonType::NAME, CreationPersonType::class, new Person(), array( @@ -326,7 +326,7 @@ final class PersonController extends AbstractController } $form = $this->createForm( - CreationPersonType::NAME, + CreationPersonType::class, $person, array( 'action' => $this->generateUrl('chill_person_review'), @@ -342,8 +342,7 @@ final class PersonController extends AbstractController $this->em->persist($person); - $alternatePersons = $this->similarPersonMatcher - ->matchPerson($person); + $alternatePersons = $this->similarPersonMatcher->matchPerson($person, $personNotDuplicateRepository); if (count($alternatePersons) === 0) { return $this->forward('ChillPersonBundle:Person:create'); diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php index aa2e2768a..d30ddebae 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php @@ -19,6 +19,7 @@ use Symfony\Component\Translation\TranslatorInterface; use Chill\ActivityBundle\Entity\Activity; use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\EventBundle\Entity\Participation; +use Chill\PersonBundle\Repository\PersonNotDuplicateRepository; use Chill\TaskBundle\Entity\SingleTask; class PersonDuplicateController extends Controller @@ -62,7 +63,7 @@ class PersonDuplicateController extends Controller $this->eventDispatcher = $eventDispatcher; } - public function viewAction($person_id) + public function viewAction($person_id, PersonNotDuplicateRepository $personNotDuplicateRepository) { $person = $this->_getPerson($person_id); if ($person === null) { @@ -74,10 +75,9 @@ class PersonDuplicateController extends Controller "You are not allowed to see this person."); $duplicatePersons = $this->similarPersonMatcher-> - matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL); + matchPerson($person, $personNotDuplicateRepository, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL); - $notDuplicatePersons = $this->getDoctrine()->getRepository(PersonNotDuplicate::class) - ->findNotDuplicatePerson($person); + $notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person); return $this->render('ChillPersonBundle:PersonDuplicate:view.html.twig', [ 'person' => $person, @@ -97,7 +97,7 @@ class PersonDuplicateController extends Controller $person1->counters = $this->_getCounters($person1_id); $person2->counters = $this->_getCounters($person2_id); - + if ($person1 === null) { throw $this->createNotFoundException("Person with id $person1_id not" . " found on this server"); @@ -264,17 +264,17 @@ class PersonDuplicateController extends Controller return [$person1, $person2]; } - + private function _getCounters($id): ?array { $em = $this->getDoctrine()->getManager(); - + $nb_activity = $em->getRepository(Activity::class)->findBy(['person'=>$id]); $nb_document = $em->getRepository(PersonDocument::class)->findBy(['person'=>$id]); $nb_event = $em->getRepository(Participation::class)->findBy(['person'=>$id]); $nb_task = $em->getRepository(SingleTask::class)->countByParameters(['person'=>$id]); $person = $em->getRepository(Person::class)->findOneBy(['id'=>$id]); - + return [ 'nb_activity' => count($nb_activity), 'nb_document' => count($nb_document), diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriod.php new file mode 100644 index 000000000..a30943bcb --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriod.php @@ -0,0 +1,88 @@ +, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Persistence\ObjectManager; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; + +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; + +/** + * Description of LoadAccompanyingPeriod + * + * @author Champs-Libres Coop + */ +class LoadAccompanyingPeriod extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface +{ + use \Symfony\Component\DependencyInjection\ContainerAwareTrait; + + + public const ACCOMPANYING_PERIOD = 'parcours 1'; + + public function getOrder() + { + return 10004; + } + + public static $references = array(); + + public function load(ObjectManager $manager) + { + + $centerA = $this->getReference('centerA'); + $centerAId = $centerA->getId(); + + $personIds = $this->container->get('doctrine.orm.entity_manager') + ->createQueryBuilder() + ->select('p.id') + ->from('ChillPersonBundle:Person', 'p') + ->where('p.center = :centerAId') + ->orderBy('p.id', 'ASC') + ->setParameter('centerAId', $centerAId) + ->getQuery() + ->getScalarResult(); + + $openingDate = new \DateTime('2020-04-01'); + + $person1 = $manager->getRepository(Person::class)->find($personIds[0]); + $person2 = $manager->getRepository(Person::class)->find($personIds[1]); + + $socialScope = $this->getReference('scope_social'); + + $a = new AccompanyingPeriod($openingDate); + $a->addPerson($person1); + $a->addPerson($person2); + $a->addScope($socialScope); + $a->setStep(AccompanyingPeriod::STEP_CONFIRMED); + + $manager->persist($a); + + $this->addReference(self::ACCOMPANYING_PERIOD, $a); + echo "Adding one AccompanyingPeriod\n"; + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php new file mode 100644 index 000000000..cbec9c439 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php @@ -0,0 +1,62 @@ +, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; + +/** + * Description of LoadAccompanyingPeriodOrigin + * + * @author Champs-Libres Coop + */ +class LoadAccompanyingPeriodOrigin extends AbstractFixture implements OrderedFixtureInterface +{ + + public const ACCOMPANYING_PERIOD_ORIGIN = 'accompanying_period_origin'; + + public function getOrder() + { + return 10005; + } + + private $phoneCall = ['en' => 'phone call', 'fr' => 'appel téléphonique']; + + public static $references = array(); + + public function load(ObjectManager $manager) + { + $o = new Origin(); + $o->setLabel(json_encode($this->phoneCall)); + + $manager->persist($o); + + $this->addReference(self::ACCOMPANYING_PERIOD_ORIGIN, $o); + echo "Adding one AccompanyingPeriod Origin\n"; + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialActions.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialActions.php new file mode 100644 index 000000000..ec47c6e0b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialActions.php @@ -0,0 +1,82 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +use Chill\PersonBundle\Entity\SocialWork\SocialAction; + +/** + * Create social actions + * + */ +class LoadSocialActions extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 10020; + } + + public static $socialActions = [ + 'social_action_info_conseil' => [ + 'title' => [ + 'fr' => 'Informer, conseiller' + ], + 'issue' => 'social_issue_prev_prot' + ], + 'social_action_instruire' => [ + 'title' => [ + 'fr' => 'Instruire l\'imprime unique pour des impayés' + ], + 'issue' => 'social_issue_prev_prot' + ], + 'social_action_MASP' => [ + 'title' => [ + 'fr' => 'MASP' + ], + 'issue' => 'social_issue_diff_fin' + ], + 'social_action_protection_enfant' => [ + 'title' => [ + 'fr' => 'Protection Enfant confié dans le cadre judiciaire' + ], + 'issue' => 'social_issue_enfant_protection' + ], + ]; + + public function load(ObjectManager $manager) + { + foreach (static::$socialActions as $ref => $new) { + $socialAction = new SocialAction(); + $socialAction->setTitle($new['title']); + $socialAction->setIssue($this->getReference($new['issue'])); + $socialAction->setDefaultNotificationDelay(new \DateInterval('P5D')); + + $manager->persist($socialAction); + $this->addReference($ref, $socialAction); + print("Adding SocialAction '".$new['title']['fr']."'\n"); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialGoals.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialGoals.php new file mode 100644 index 000000000..a6c2b0ef0 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialGoals.php @@ -0,0 +1,70 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +use Chill\PersonBundle\Entity\SocialWork\Goal; + + +/** + * Create social goals + * + */ +class LoadSocialGoals extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 10030; + } + + public static $socialGoals = [ + 'social_goal_instuire_dossier' => [ + 'title' => [ + 'fr' => 'Instruire le dossier de surendettement' + ], + 'action' => 'social_action_MASP' + ], + 'social_goal_proteger' => [ + 'title' => [ + 'fr' => 'Protéger via une assistance educative placement' + ], + 'action' => 'social_action_protection_enfant' + ], + ]; + + public function load(ObjectManager $manager) + { + foreach (static::$socialGoals as $ref => $new) { + $socialGoal = new Goal(); + $socialGoal->setTitle($new['title']); + $socialGoal->addSocialAction($this->getReference($new['action'])); + + $manager->persist($socialGoal); + $this->addReference($ref, $socialGoal); + print("Adding SocialGoal '".$new['title']['fr']."'\n"); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialIssues.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialIssues.php new file mode 100644 index 000000000..eb9c303ed --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialIssues.php @@ -0,0 +1,90 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +use Chill\PersonBundle\Entity\SocialWork\SocialIssue; + +/** + * Create social issues + * + */ +class LoadSocialIssues extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 10010; + } + + public static $socialIssues = [ + 'social_issue_diff_fin_or_admin' => [ + 'title' => [ + 'fr' => 'ADULTE - DIFFICULTES FINANCIERES ET/OU ADMINISTRATIVES' + ] + ], + 'social_issue_prev_prot' => [ + 'title' => [ + 'fr' => 'ADULTE PREVENTION/PROTECTION' + ], + 'parent' => 'social_issue_diff_fin_or_admin' + ], + 'social_issue_diff_fin' => [ + 'title' => [ + 'fr' => 'Difficulté financière' + ], + 'parent' => 'social_issue_diff_fin_or_admin' + ], + 'social_issue_enfant_famille' => [ + 'title' => [ + 'fr' => 'Enfant / famille' + ] + ], + 'social_issue_enfant_protection' => [ + 'title' => [ + 'fr' => 'enfant - protection' + ], + 'parent' => 'social_issue_enfant_famille' + ], + ]; + + public function load(ObjectManager $manager) + { + foreach (static::$socialIssues as $ref => $new) { + $socialIssue = new SocialIssue(); + $socialIssue->setTitle($new['title']); + + if ( array_key_exists('parent', $new)) { + $parentRef = $new['parent']; + $parent = $this->getReference($parentRef); + $socialIssue->setParent($parent); + } + + $manager->persist($socialIssue); + $this->addReference($ref, $socialIssue); + print("Adding SocialIssue '".$new['title']['fr']."'\n"); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialResults.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialResults.php new file mode 100644 index 000000000..573313573 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialResults.php @@ -0,0 +1,94 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +use Chill\PersonBundle\Entity\SocialWork\Result; + + +/** + * Create social results + * + */ +class LoadSocialResults extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 10040; + } + + public static $socialResults = [ + 'social_result_FSL_acces' => [ + 'title' => [ + 'fr' => 'FSL - accès cautionnement' + ], + 'action' => 'social_action_instruire' + ], + 'social_result_FSL_maintien' => [ + 'title' => [ + 'fr' => 'FSL maintien - impayés de loyer' + ], + 'action' => 'social_action_MASP' + ], + 'social_result_soutien_parental' => [ + 'title' => [ + 'fr' => 'Soutien parental' + ], + // 'action' => 'social_action_protection_enfant', (via le goal) + 'goal' => 'social_goal_proteger' + ], + 'social_result_accompagnement_mineur' => [ + 'title' => [ + 'fr' => 'Accompagnement du mineur' + ], + // 'action' => 'social_action_protection_enfant', (via le goal) + 'goal' => 'social_goal_proteger', + ], + ]; + + public function load(ObjectManager $manager) + { + foreach (static::$socialResults as $ref => $new) { + $socialResult = new Result(); + $socialResult->setTitle($new['title']); + + if ( array_key_exists('action', $new)) { + $action = $this->getReference($new['action']); + $socialResult->addSocialAction($action); + } + + if ( array_key_exists('goal', $new)) { + $goal = $this->getReference($new['goal']); + $socialResult->addGoal($goal); + } + + + $manager->persist($socialResult); + $this->addReference($ref, $socialResult); + print("Adding SocialResult '".$new['title']['fr']."'\n"); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 84b8bb397..27721012d 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -1,5 +1,4 @@ * @@ -73,9 +72,11 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/command.yaml'); $loader->load('services/actions.yaml'); $loader->load('services/form.yaml'); - $loader->load('services/repository.yaml'); $loader->load('services/templating.yaml'); $loader->load('services/alt_names.yaml'); + // We can get rid of this file when the service 'chill.person.repository.person' is no more used. + // We should use the PersonRepository service instead of a custom service name. + $loader->load('services/repository.yaml'); $loader->load('services/serializer.yaml'); $loader->load('services/security.yaml'); @@ -164,6 +165,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $this->prependHomepageWidget($container); $this->prependDoctrineDQL($container); $this->prependCruds($container); + $this->prependWorkflows($container); //add person_fields parameter as global $chillPersonConfig = $container->getExtensionConfig($this->getAlias()); @@ -193,6 +195,39 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac )); } + protected function prependWorkflows(ContainerBuilder $container) + { + $container->prependExtensionConfig('framework', [ + 'workflows' => [ + 'accompanying_period_lifecycle' => [ + 'type' => 'state_machine', + 'audit_trail' => [ + 'enabled' => true + ], + 'marking_store' => [ + 'type' => 'method', + 'property' => 'step', + ], + 'supports' => [ + 'Chill\PersonBundle\Entity\AccompanyingPeriod' + ], + 'initial_marking' => 'DRAFT', + 'places' => [ + 'DRAFT', + 'CONFIRMED', + ], + 'transitions' => [ + 'confirm' => [ + 'from' => 'DRAFT', + 'to' => 'CONFIRMED' + ], + ], + ], + ] + ]); + + } + /** * Add a widget "add a person" on the homepage, automatically * @@ -320,10 +355,29 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'actions' => [ '_entity' => [ 'roles' => [ - Request::METHOD_GET => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE + Request::METHOD_GET => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_PATCH => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_PUT => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + ], + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_PUT => true, + Request::METHOD_PATCH => true, ] ], 'participation' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_DELETE => true, + Request::METHOD_GET => false, + Request::METHOD_HEAD => false, + ], + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE + ] + ], + 'resource' => [ 'methods' => [ Request::METHOD_POST => true, Request::METHOD_DELETE => true, @@ -334,8 +388,67 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE ] - ] - + ], + 'comment' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_DELETE => true, + Request::METHOD_GET => false, + Request::METHOD_HEAD => false, + ], + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE + ] + ], + 'requestor' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_DELETE => true, + Request::METHOD_GET => false, + Request::METHOD_HEAD => false, + ], + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE + ] + ], + 'scope' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_DELETE => true, + Request::METHOD_GET => false, + Request::METHOD_HEAD => false, + ], + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE + ] + ], + 'socialissue' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_DELETE => true, + Request::METHOD_GET => false, + Request::METHOD_HEAD => false, + ], + 'controller_action' => 'socialIssueApi', + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE + ] + ], + + 'confirm' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_GET => false, + Request::METHOD_HEAD => false, + ], + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + ] + ], ] ], [ @@ -346,19 +459,61 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'base_role' => 'ROLE_USER', 'actions' => [ '_index' => [ - 'methods' => [ + 'methods' => [ Request::METHOD_GET => true, Request::METHOD_HEAD => true ], ], '_entity' => [ - 'methods' => [ + 'methods' => [ Request::METHOD_GET => true, Request::METHOD_HEAD => true ] ], ] - ] + ], + [ + 'class' => \Chill\PersonBundle\Entity\SocialWork\SocialIssue::class, + 'name' => 'social_work_social_issue', + 'base_path' => '/api/1.0/person/social-work/social-issue', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ] + ], + ] + ], + [ + 'class' => \Chill\PersonBundle\Entity\Person::class, + 'name' => 'person', + 'base_path' => '/api/1.0/person/person', + 'base_role' => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE, + 'controller' => \Chill\PersonBundle\Controller\PersonApiController::class, + 'actions' => [ + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + Request::METHOD_POST=> true, + ], + 'roles' => [ + Request::METHOD_GET => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE, + Request::METHOD_HEAD => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE, + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\PersonVoter::CREATE, + + ] + ], + ] + ], ] ]); } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 68f5d76e6..9841a99f3 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -22,25 +22,34 @@ namespace Chill\PersonBundle\Entity; +use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; +use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Entity\Scope; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; +use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Chill\MainBundle\Entity\User; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; /** * AccompanyingPeriod Class * - * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\AccompanyingPeriodRepository") + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period") + * @DiscriminatorMap(typeProperty="type", mapping={ + * "accompanying_period"=AccompanyingPeriod::class + * }) */ -class AccompanyingPeriod +class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface { /** * Mark an accompanying period as "occasional" @@ -80,6 +89,7 @@ class AccompanyingPeriod * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") + * @Groups({"read"}) */ private $id; @@ -87,6 +97,7 @@ class AccompanyingPeriod * @var \DateTime * * @ORM\Column(type="date") + * @Groups({"read", "write"}) */ private $openingDate; @@ -94,6 +105,7 @@ class AccompanyingPeriod * @var \DateTime * * @ORM\Column(type="date", nullable=true) + * @Groups({"read", "write"}) */ private $closingDate = null; @@ -101,6 +113,7 @@ class AccompanyingPeriod * @var string * * @ORM\Column(type="text") + * @Groups({"read", "write"}) */ private $remark = ''; @@ -108,17 +121,28 @@ class AccompanyingPeriod * @var Collection * * @ORM\OneToMany(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Comment", - * mappedBy="accompanyingPeriod" + * mappedBy="accompanyingPeriod", + * cascade={"persist", "remove"}, + * orphanRemoval=true * ) */ private $comments; + /** + * @ORM\ManyToOne( + * targetEntity=Comment::class + * ) + * @Groups({"read"}) + */ + private ?Comment $initialComment = null; + /** * @var Collection * * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, * mappedBy="accompanyingPeriod", * cascade={"persist", "refresh", "remove", "merge", "detach"}) + * @Groups({"read"}) */ private $participations; @@ -128,36 +152,42 @@ class AccompanyingPeriod * @ORM\ManyToOne( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive") * @ORM\JoinColumn(nullable=true) + * @Groups({"read", "write"}) */ private $closingMotive = null; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=true) + * @Groups({"read", "write"}) */ private $user; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=true) + * @Groups({"read"}) */ private $createdBy; /** * @var string * @ORM\Column(type="string", length=32, nullable=true) + * @Groups({"read"}) */ private $step = self::STEP_DRAFT; /** * @ORM\ManyToOne(targetEntity=Origin::class) * @ORM\JoinColumn(nullable=true) + * @Groups({"read", "write"}) */ private $origin; /** * @var string * @ORM\Column(type="string", nullable=true) + * @Groups({"read", "write"}) */ private $intensity; @@ -172,6 +202,7 @@ class AccompanyingPeriod * joinColumns={@ORM\JoinColumn(name="accompanying_period_id", referencedColumnName="id")}, * inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")} * ) + * @Groups({"read"}) */ private $scopes; @@ -189,19 +220,22 @@ class AccompanyingPeriod /** * @var bool - * @ORM\Column(type="boolean") + * @ORM\Column(type="boolean", options={"default": false} ) + * @Groups({"read", "write"}) */ private $requestorAnonymous = false; /** * @var bool - * @ORM\Column(type="boolean") + * @ORM\Column(type="boolean", options={"default": false} ) + * @Groups({"read", "write"}) */ private $emergency = false; /** * @var bool - * @ORM\Column(type="boolean") + * @ORM\Column(type="boolean", options={"default": false} ) + * @Groups({"read", "write"}) */ private $confidential = false; @@ -210,21 +244,54 @@ class AccompanyingPeriod * * @ORM\OneToMany( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Resource", - * mappedBy="accompanyingPeriod" + * mappedBy="accompanyingPeriod", + * cascade={"persist", "remove"}, + * orphanRemoval=true * ) + * @Groups({"read"}) */ private $resources; + /** + * @ORM\ManyToMany( + * targetEntity=SocialIssue::class + * ) + * @ORM\JoinTable( + * name="chill_person_accompanying_period_social_issues" + * ) + * @Groups({"read"}) + */ + private Collection $socialIssues; + + /** + * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) + */ + private \DateTimeInterface $createdAt; + + /** + * @ORM\ManyToOne( + * targetEntity=User::class + * ) + */ + private User $updatedBy; + + /** + * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) + */ + private \DateTimeInterface $updatedAt; + /** * AccompanyingPeriod constructor. * * @param \DateTime $dateOpening * @uses AccompanyingPeriod::setClosingDate() */ - public function __construct(\DateTime $dateOpening) { - $this->setOpeningDate($dateOpening); + public function __construct(\DateTime $dateOpening = null) { + $this->setOpeningDate($dateOpening ?? new \DateTime('now')); $this->participations = new ArrayCollection(); $this->scopes = new ArrayCollection(); + $this->socialIssues = new ArrayCollection(); + $this->comments = new ArrayCollection(); } /** @@ -318,23 +385,55 @@ class AccompanyingPeriod return $this->remark; } + /** + * @Groups({"read"}) + */ public function getComments(): Collection { - return $this->comments; + return $this->comments->filter(function (Comment $c) { + return $c !== $this->initialComment; + }); } public function addComment(Comment $comment): self { $this->comments[] = $comment; + $comment->setAccompanyingPeriod($this); return $this; } public function removeComment(Comment $comment): void { + $comment->setAccompanyingPeriod(null); $this->comments->removeElement($comment); } + /** + * @Groups({"write"}) + */ + public function setInitialComment(?Comment $comment = null): self + { + if (NULL !== $this->initialComment) { + $this->removeComment($this->initialComment); + } + if ($comment instanceof Comment) { + $this->addComment($comment); + } + + $this->initialComment = $comment; + + return $this; + } + + /** + * @Groups({"read"}) + */ + public function getInitialComment(): ?Comment + { + return $this->initialComment; + } + /** * Get Participations Collection */ @@ -458,7 +557,7 @@ class AccompanyingPeriod return false; } - $participation = $this->participationsContainsPerson($person); + $participation = $this->getParticipationsContainsPerson($person); if (!null === $participation) { $person = $participation->getPerson(); @@ -540,9 +639,9 @@ class AccompanyingPeriod return $this->requestorPerson; } - public function setRequestorPerson(Person $requestorPerson): self + private function setRequestorPerson(Person $requestorPerson = null): self { - $this->requestorPerson = ($this->requestorThirdParty === null) ? $requestorPerson : null; + $this->requestorPerson = $requestorPerson; return $this; } @@ -552,21 +651,53 @@ class AccompanyingPeriod return $this->requestorThirdParty; } - public function setRequestorThirdParty(ThirdParty $requestorThirdParty): self + private function setRequestorThirdParty(ThirdParty $requestorThirdParty = null): self { - $this->requestorThirdParty = ($this->requestorPerson === null) ? $requestorThirdParty : null; + $this->requestorThirdParty = $requestorThirdParty; return $this; } /** * @return Person|ThirdParty + * @Groups({"read"}) */ public function getRequestor() { return $this->requestorPerson ?? $this->requestorThirdParty; } + + /** + * Set a requestor + * + * The requestor is either an instance of ThirdParty, or an + * instance of Person + * + * @param $requestor Person|ThirdParty + * @return self + * @throw UnexpectedValueException if the requestor is not a Person or ThirdParty + * @Groups({"write"}) + */ + public function setRequestor($requestor): self + { + if ($requestor instanceof Person) { + $this->setRequestorThirdParty(NULL); + $this->setRequestorPerson($requestor); + } elseif ($requestor instanceof ThirdParty) { + $this->setRequestorThirdParty($requestor); + $this->setRequestorPerson(NULL); + } elseif (NULL === $requestor) { + $this->setRequestorPerson(NULL); + $this->setRequestorThirdParty(NULL); + } else { + throw new \UnexpectedValueException("requestor is not an instance of Person or ThirdParty"); + } + + return $this; + } + + public function isRequestorAnonymous(): bool { return $this->requestorAnonymous; @@ -663,6 +794,7 @@ class AccompanyingPeriod public function addResource(Resource $resource): self { + $resource->setAccompanyingPeriod($this); $this->resources[] = $resource; return $this; @@ -670,9 +802,27 @@ class AccompanyingPeriod public function removeResource(Resource $resource): void { + $resource->setAccompanyingPeriod(null); $this->resources->removeElement($resource); } + public function getSocialIssues(): Collection + { + return $this->socialIssues; + } + + public function addSocialIssue(SocialIssue $socialIssue): self + { + $this->socialIssues[] = $socialIssue; + + return $this; + } + + public function removeSocialIssue(SocialIssue $socialIssue): void + { + $this->socialIssues->removeElement($socialIssue); + } + /** * Get a list of all persons which are participating to this course */ @@ -684,4 +834,25 @@ class AccompanyingPeriod } ); } + + public function setCreatedAt(\DateTimeInterface $datetime): self + { + $this->createdAt = $datetime; + + return $this; + } + + public function setUpdatedBy(User $user): self + { + $this->updatedBy = $user; + + return $this; + } + + public function setUpdatedAt(\DateTimeInterface $datetime): self + { + $this->updatedAt = $datetime; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index 6983f6c0e..25fbcc342 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -4,7 +4,6 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\SocialWork\Result; use Chill\PersonBundle\Entity\SocialWork\SocialAction; -use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\ThirdPartyBundle\Entity\ThirdParty; @@ -13,7 +12,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=AccompanyingPeriodWorkRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_work") */ class AccompanyingPeriodWork diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php index ec371d6c2..955f9e3a1 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php @@ -4,13 +4,12 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\SocialWork\Goal; use Chill\PersonBundle\Entity\SocialWork\Result; -use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkGoalRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=AccompanyingPeriodWorkGoalRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_work_goal") */ class AccompanyingPeriodWorkGoal diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php index 4e334b2aa..29b2a44aa 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php @@ -29,8 +29,7 @@ use Doctrine\Common\Collections\ArrayCollection; /** * ClosingMotive give an explanation why we closed the Accompanying period * - * @ORM\Entity( - * repositoryClass="Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepository") + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_closingmotive") */ class ClosingMotive diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php index e8ecf3248..18b5bd38d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php @@ -22,21 +22,28 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Repository\AccompanyingPeriod\CommentRepository; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; +use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; /** - * @ORM\Entity(repositoryClass=CommentRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_comment") + * @DiscriminatorMap(typeProperty="type", mapping={ + * "accompanying_period_comment"=Comment::class + * }) */ -class Comment +class Comment implements TrackCreationInterface, TrackUpdateInterface { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Groups({"read"}) */ private $id; @@ -44,34 +51,39 @@ class Comment * @ORM\ManyToOne( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod", * inversedBy="comments") - * @ORM\JoinColumn(nullable=false) + * @ORM\JoinColumn(nullable=false, onDelete="CASCADE") */ private $accompanyingPeriod; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=false) + * @Groups({"read"}) */ private $creator; /** * @ORM\Column(type="datetime") + * @Groups({"read"}) */ private $createdAt; /** * @ORM\Column(type="datetime") + * @Groups({"read"}) */ private $updatedAt; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=false) + * @Groups({"read"}) */ private $updatedBy; /** * @ORM\Column(type="text") + * @Groups({"read", "write"}) */ private $content; @@ -104,6 +116,11 @@ class Comment return $this; } + public function setCreatedBy(User $user): self + { + return $this->setCreator($user); + } + public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; @@ -133,7 +150,7 @@ class Comment return $this->updatedBy; } - public function setUpdatedBy(?User $updatedBy): self + public function setUpdatedBy(User $updatedBy): self { $this->updatedBy = $updatedBy; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php index 55857de4c..3175e3156 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php @@ -22,11 +22,11 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Repository\AccompanyingPeriod\OriginRepository; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; /** - * @ORM\Entity(repositoryClass=OriginRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_origin") */ class Origin @@ -35,16 +35,19 @@ class Origin * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Groups({"read"}) */ private $id; /** * @ORM\Column(type="json") + * @Groups({"read"}) */ private $label; /** * @ORM\Column(type="date_immutable", nullable=true) + * @Groups({"read"}) */ private $noActiveAfter; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php index ec13fcad6..cf796722a 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php @@ -22,16 +22,21 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Repository\AccompanyingPeriod\ResourceRepository; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Repository\AccompanyingPeriod\ResourceRepository; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Annotation\Groups; /** * @ORM\Entity(repositoryClass=ResourceRepository::class) * @ORM\Table(name="chill_person_accompanying_period_resource") + * @DiscriminatorMap(typeProperty="type", mapping={ + * "accompanying_period_resource"=Resource::class + * }) */ class Resource { @@ -39,6 +44,7 @@ class Resource * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Groups({"read"}) */ private $id; @@ -91,7 +97,7 @@ class Resource return $this->thirdParty; } - public function setThirdParty(?ThirdParty $thirdParty): self + private function setThirdParty(?ThirdParty $thirdParty): self { $this->thirdParty = $thirdParty; @@ -103,7 +109,7 @@ class Resource return $this->person; } - public function setPerson(?Person $person): self + private function setPerson(?Person $person): self { $this->person = $person; @@ -121,9 +127,35 @@ class Resource return $this; } + + /** + * + * @param $resource Person|ThirdParty + */ + public function setResource($resource): self + { + if ($resource instanceof ThirdParty) { + $this->setThirdParty($resource); + $this->setPerson(NULL); + } elseif ($resource instanceof Person) { + $this->setPerson($resource); + $this->setThirdParty(NULL); + } elseif (NULL === $resource) { + $this->setPerson(NULL); + $this->setThirdParty(NULL); + } else { + throw new \UnexpectedValueException(sprintf("the resource ". + "should be an instance of %s or %s", Person::class, + ThirdParty::class)); + } + + return $this; + } + /** - * @return Person|ThirdParty + * @return ThirdParty|Person + * @Groups({"read", "write"}) */ public function getResource() { diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php index c22e84e42..70c5a0505 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php @@ -22,17 +22,21 @@ namespace Chill\PersonBundle\Entity; -use Chill\PersonBundle\Repository\AccompanyingPeriodParticipationRepository; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; /** * AccompanyingPeriodParticipation Class * * @package Chill\PersonBundle\Entity - * @ORM\Entity(repositoryClass=AccompanyingPeriodParticipationRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_participation") + * @DiscriminatorMap(typeProperty="type", mapping={ + * "accompanying_period_participation"=AccompanyingPeriodParticipation::class + * }) */ class AccompanyingPeriodParticipation { @@ -40,12 +44,14 @@ class AccompanyingPeriodParticipation * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Groups({"read"}) */ private $id; /** * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodParticipations") * @ORM\JoinColumn(name="person_id", referencedColumnName="id", nullable=false) + * @Groups({"read"}) */ private $person; @@ -57,11 +63,13 @@ class AccompanyingPeriodParticipation /** * @ORM\Column(type="date", nullable=false) + * @Groups({"read"}) */ private $startDate; /** * @ORM\Column(type="date", nullable=true) + * @Groups({"read"}) */ private $endDate = null; diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index a12741dee..4e8787a96 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -2,13 +2,12 @@ namespace Chill\PersonBundle\Entity\Household; -use Chill\PersonBundle\Repository\Household\HouseholdRepository; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\Collection; use Chill\MainBundle\Entity\Address; /** - * @ORM\Entity(repositoryClass=HouseholdRepository::class) + * @ORM\Entity */ class Household { diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php index 80864ecd0..5d16649a8 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php @@ -3,12 +3,11 @@ namespace Chill\PersonBundle\Entity\Household; use Doctrine\ORM\Mapping as ORM; -use Chill\PersonBundle\Repository\Household\HouseholdMembersRepository; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; /** - * @ORM\Entity(repositoryClass=HouseholdMembersRepository::class) + * @ORM\Entity */ class HouseholdMembers { diff --git a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php index f1addbb2c..43ae1b09f 100644 --- a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php +++ b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php @@ -25,7 +25,7 @@ use Doctrine\ORM\Mapping as ORM; /** * MaritalStatus * - * @ORM\Entity() + * @ORM\Entity * @ORM\Table(name="chill_person_marital_status") * @ORM\HasLifecycleCallbacks() */ diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 3624d4c17..1afa6278c 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -34,17 +34,21 @@ use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; /** * Person Class * - * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\PersonRepository") + * @ORM\Entity * @ORM\Table(name="chill_person_person", * indexes={@ORM\Index( * name="person_names", * columns={"firstName", "lastName"} * )}) * @ORM\HasLifecycleCallbacks() + * @DiscriminatorMap(typeProperty="type", mapping={ + * "person"=Person::class + * }) */ class Person implements HasCenterInterface { diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php b/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php index c5295487e..95603d4ef 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php @@ -8,7 +8,7 @@ use Doctrine\ORM\Mapping as ORM; * PersonAltName * * @ORM\Table(name="chill_person_alt_name") - * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\PersonAltNameRepository") + * @ORM\Entity */ class PersonAltName { diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php b/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php index ce2a1c26f..9f116c501 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php @@ -9,7 +9,7 @@ use Chill\MainBundle\Entity\User; * PersonNotDuplicate * * @ORM\Table(name="chill_person_not_duplicate") - * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\PersonNotDuplicateRepository") + * @ORM\Entity */ class PersonNotDuplicate { diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php b/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php index 4a2ffaf28..222495e17 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php @@ -8,7 +8,7 @@ use Doctrine\ORM\Mapping as ORM; /** * Person Phones * - * @ORM\Entity() + * @ORM\Entity * @ORM\Table(name="chill_person_phone") */ class PersonPhone diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php index 9e6dcbaa6..5a877e264 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php @@ -2,11 +2,10 @@ namespace Chill\PersonBundle\Entity\SocialWork; -use Chill\PersonBundle\Repository\SocialWork\EvaluationRepository; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=EvaluationRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_social_work_evaluation") */ class Evaluation diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php index c92e9a736..996fcf3eb 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php @@ -2,13 +2,12 @@ namespace Chill\PersonBundle\Entity\SocialWork; -use Chill\PersonBundle\Repository\SocialWork\GoalRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=GoalRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_social_work_goal") */ class Goal diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php index be38b1757..37e5c2b18 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php @@ -4,13 +4,12 @@ namespace Chill\PersonBundle\Entity\SocialWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal; -use Chill\PersonBundle\Repository\SocialWork\ResultRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=ResultRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_social_work_result") */ class Result diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php index 64413d987..ff7291b45 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php @@ -2,13 +2,12 @@ namespace Chill\PersonBundle\Entity\SocialWork; -use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=SocialActionRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_social_action") */ class SocialAction diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php index cfec01751..6b2152cdf 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php @@ -1,15 +1,18 @@ parent; } + public function hasParent(): bool + { + return $this->parent !== null; + } + public function setParent(?self $parent): self { $this->parent = $parent; diff --git a/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php b/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php index ec3ddbf82..e3583071f 100644 --- a/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php @@ -33,9 +33,10 @@ use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Form\Type\PersonAltNameType; -class CreationPersonType extends AbstractType +final class CreationPersonType extends AbstractType { - + // TODO: This is only used in test. + // TODO: See if this is still valid and update accordingly. const NAME = 'chill_personbundle_person_creation'; const FORM_NOT_REVIEWED = 'not_reviewed'; diff --git a/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php index 74052e87c..e9ce20b53 100644 --- a/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -3,6 +3,7 @@ namespace Chill\PersonBundle\Menu; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Knp\Menu\MenuItem; use Symfony\Contracts\Translation\TranslatorInterface; @@ -32,24 +33,31 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface public function buildMenu($menuId, MenuItem $menu, array $parameters): void { + $period = $parameters['accompanyingCourse']; + $menu->addChild($this->translator->trans('Resume Accompanying Course'), [ 'route' => 'chill_person_accompanying_course_index', 'routeParameters' => [ - 'accompanying_period_id' => $parameters['accompanyingCourse']->getId() + 'accompanying_period_id' => $period->getId() ]]) ->setExtras(['order' => 10]); $menu->addChild($this->translator->trans('Edit Accompanying Course'), [ 'route' => 'chill_person_accompanying_course_show', 'routeParameters' => [ - 'accompanying_period_id' => $parameters['accompanyingCourse']->getId() + 'accompanying_period_id' => $period->getId() ]]) ->setExtras(['order' => 20]); + if (AccompanyingPeriod::STEP_DRAFT === $period->getStep()) { + // no more menu items if the period is draft + return; + } + $menu->addChild($this->translator->trans('Accompanying Course Details'), [ 'route' => 'chill_person_accompanying_course_history', 'routeParameters' => [ - 'accompanying_period_id' => $parameters['accompanyingCourse']->getId() + 'accompanying_period_id' => $period->getId() ]]) ->setExtras(['order' => 30]); } diff --git a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php index ea6a1d060..0b307204d 100644 --- a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php @@ -71,6 +71,14 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface 'icons' => [ 'plus' ] ]); } + + $menu->addChild($this->translator->trans('Create an accompanying course'), [ + 'route' => 'chill_person_accompanying_course_new' + ]) + ->setExtras([ + 'order' => 11, + 'icons' => [ 'plus' ] + ]); } /** diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php index fc8233d23..d9fa5b6e3 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method AccompanyingPeriodWorkGoal|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method AccompanyingPeriodWorkGoal[] findAll() * @method AccompanyingPeriodWorkGoal[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class AccompanyingPeriodWorkGoalRepository extends ServiceEntityRepository +final class AccompanyingPeriodWorkGoalRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, AccompanyingPeriodWorkGoal::class); + $this->repository = $entityManager->getRepository(AccompanyingPeriodWorkGoal::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php index f879e97d7..5d455b617 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method AccompanyingPeriodWork|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method AccompanyingPeriodWork[] findAll() * @method AccompanyingPeriodWork[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class AccompanyingPeriodWorkRepository extends ServiceEntityRepository +final class AccompanyingPeriodWorkRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, AccompanyingPeriodWork::class); + $this->repository = $entityManager->getRepository(AccompanyingPeriodWork::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php index ef1130b62..c99ca8efc 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php @@ -22,8 +22,9 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; +use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Query\ResultSetMappingBuilder; /** @@ -32,17 +33,24 @@ use Doctrine\ORM\Query\ResultSetMappingBuilder; * * @package Chill\PersonBundle\Repository */ -class ClosingMotiveRepository extends EntityRepository +final class ClosingMotiveRepository { + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(ClosingMotive::class); + } + /** * @param bool $onlyLeaf * @return mixed */ public function getActiveClosingMotive(bool $onlyLeaf = true) { - $rsm = new ResultSetMappingBuilder($this->getEntityManager()); - $rsm->addRootEntityFromClassMetadata($this->getClassName(), 'cm'); - + $rsm = new ResultSetMappingBuilder($this->repository->getEntityManager()); + $rsm->addRootEntityFromClassMetadata($this->repository->getClassName(), 'cm'); + $sql = "SELECT ".(string) $rsm." FROM chill_person_accompanying_period_closingmotive AS cm WHERE @@ -55,10 +63,11 @@ class ClosingMotiveRepository extends EntityRepository } $sql .= " ORDER BY cm.ordering ASC"; - - return $this->_em + + return $this + ->repository + ->getEntityManager() ->createNativeQuery($sql, $rsm) - ->getResult() - ; + ->getResult(); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php index b41e77591..aca7a9c68 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php @@ -23,8 +23,8 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Comment|null find($id, $lockMode = null, $lockVersion = null) @@ -32,11 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Comment[] findAll() * @method Comment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class CommentRepository extends ServiceEntityRepository +final class CommentRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Comment::class); + $this->repository = $entityManager->getRepository(Comment::class); } - } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php index 6a5b28901..c2851b851 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php @@ -23,8 +23,8 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Origin|null find($id, $lockMode = null, $lockVersion = null) @@ -32,11 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Origin[] findAll() * @method Origin[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class OriginRepository extends ServiceEntityRepository +final class OriginRepository { - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, Origin::class); - } + private EntityRepository $repository; + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(Origin::class); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php index 46eaaaaa0..c32f74762 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php @@ -23,6 +23,7 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; +use Doctrine\ORM\EntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -32,11 +33,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Resource[] findAll() * @method Resource[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class ResourceRepository extends ServiceEntityRepository +final class ResourceRepository extends ServiceEntityRepository { + private EntityRepository $repository; + public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Resource::class); } - } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php index fbe957ecf..0f157ed42 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php @@ -23,8 +23,8 @@ namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method AccompanyingPeriodParticipation|null find($id, $lockMode = null, $lockVersion = null) @@ -32,11 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method AccompanyingPeriodParticipation[] findAll() * @method AccompanyingPeriodParticipation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class AccompanyingPeriodParticipationRepository extends ServiceEntityRepository +final class AccompanyingPeriodParticipationRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, AccompanyingPeriodParticipation::class); + $this->repository = $entityManager->getRepository(AccompanyingPeriodParticipation::class); } - } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index fffb55ede..07aa0a55f 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -23,9 +23,8 @@ namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Entity\Person; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method AccompanyingPeriod|null find($id, $lockMode = null, $lockVersion = null) @@ -33,10 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method AccompanyingPeriod[] findAll() * @method AccompanyingPeriod[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class AccompanyingPeriodRepository extends ServiceEntityRepository +final class AccompanyingPeriodRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, AccompanyingPeriod::class); + $this->repository = $entityManager->getRepository(AccompanyingPeriod::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdMembersRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdMembersRepository.php index feea6d44d..b11d3d93a 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdMembersRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdMembersRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\Household; use Chill\PersonBundle\Entity\Household\HouseholdMembers; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method HouseholdMembers|null find($id, $lockMode = null, $lockVersion = null) @@ -12,11 +12,13 @@ use Doctrine\Persistence\ManagerRegistry; * @method HouseholdMembers[] findAll() * @method HouseholdMembers[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class HouseholdMembersRepository extends ServiceEntityRepository +final class HouseholdMembersRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, HouseholdMembers::class); + $this->repository = $entityManager->getRepository(HouseholdMembers::class); } // /** diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php index 38522806c..78a68f56d 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\Household; use Chill\PersonBundle\Entity\Household\Household; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Household|null find($id, $lockMode = null, $lockVersion = null) @@ -12,11 +12,13 @@ use Doctrine\Persistence\ManagerRegistry; * @method Household[] findAll() * @method Household[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class HouseholdRepository extends ServiceEntityRepository +final class HouseholdRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Household::class); + $this->repository = $entityManager->getRepository(Household::class); } // /** diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php index 315cee94f..c5dea689a 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php @@ -2,12 +2,22 @@ namespace Chill\PersonBundle\Repository; +use Chill\PersonBundle\Entity\PersonAltName; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; + /** * PersonAltNameRepository * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ -class PersonAltNameRepository extends \Doctrine\ORM\EntityRepository +final class PersonAltNameRepository { + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(PersonAltName::class); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonNotDuplicateRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonNotDuplicateRepository.php index 8ab711b15..989baaeec 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonNotDuplicateRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonNotDuplicateRepository.php @@ -4,6 +4,7 @@ namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\PersonNotDuplicate; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; /** @@ -11,8 +12,15 @@ use Doctrine\ORM\EntityRepository; * * @package Chill\PersonBundle\Repository */ -class PersonNotDuplicateRepository extends EntityRepository +final class PersonNotDuplicateRepository { + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(PersonNotDuplicate::class); + } + /** * @param \Chill\PersonBundle\Entity\Person $person * @@ -20,7 +28,7 @@ class PersonNotDuplicateRepository extends EntityRepository */ public function findNotDuplicatePerson(Person $person) { - $qb = $this->createQueryBuilder('pnd'); + $qb = $this->repository->createQueryBuilder('pnd'); $qb->select('pnd') ->where('pnd.person1 = :person OR pnd.person2 = :person') ; diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php index ccc56b639..12733a668 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php @@ -18,16 +18,25 @@ namespace Chill\PersonBundle\Repository; +use Chill\PersonBundle\Entity\Person; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; -/** - * Class PersonRepository - * - * @package Chill\PersonBundle\Repository - */ -class PersonRepository extends EntityRepository +final class PersonRepository { + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(Person::class); + } + + public function find($id, $lockMode = null, $lockVersion = null) + { + return $this->repository->find($id, $lockMode, $lockVersion); + } + /** * @param string $phonenumber * @param $centers @@ -44,7 +53,7 @@ class PersonRepository extends EntityRepository $maxResults, array $only = ['mobile', 'phone'] ) { - $qb = $this->createQueryBuilder('p'); + $qb = $this->repository->createQueryBuilder('p'); $qb->select('p'); $this->addByCenters($qb, $centers); @@ -71,7 +80,7 @@ class PersonRepository extends EntityRepository array $only = ['mobile', 'phone'] ): int { - $qb = $this->createQueryBuilder('p'); + $qb = $this->repository->createQueryBuilder('p'); $qb->select('COUNT(p)'); $this->addByCenters($qb, $centers); diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php index b030e1617..c9ab3c931 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\Evaluation; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Evaluation|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Evaluation[] findAll() * @method Evaluation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class EvaluationRepository extends ServiceEntityRepository +final class EvaluationRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Evaluation::class); + $this->repository = $entityManager->getRepository(Evaluation::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php index 031c31764..30576b4ed 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\Goal; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Goal|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Goal[] findAll() * @method Goal[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class GoalRepository extends ServiceEntityRepository +final class GoalRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Goal::class); + $this->repository = $entityManager->getRepository(Goal::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php index 511823d61..004a2f82c 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\Result; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Result|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Result[] findAll() * @method Result[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class ResultRepository extends ServiceEntityRepository +final class ResultRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Result::class); + $this->repository = $entityManager->getRepository(Result::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php index f0f2e81b4..cfa9adbd7 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\SocialAction; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method SocialAction|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method SocialAction[] findAll() * @method SocialAction[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class SocialActionRepository extends ServiceEntityRepository +final class SocialActionRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, SocialAction::class); + $this->repository = $entityManager->getRepository(SocialAction::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php index e64b057f4..2f6eb3ba1 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php @@ -4,7 +4,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method SocialIssue|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +13,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method SocialIssue[] findAll() * @method SocialIssue[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class SocialIssueRepository extends ServiceEntityRepository +final class SocialIssueRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, SocialIssue::class); + $this->repository = $entityManager->getRepository(SocialIssue::class); } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/App.vue deleted file mode 100644 index 971eca1c7..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/App.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/AccompanyingCourse.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/AccompanyingCourse.vue deleted file mode 100644 index d08798c00..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/AccompanyingCourse.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonItem.vue deleted file mode 100644 index f75f0779b..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonItem.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonsAssociated.vue deleted file mode 100644 index f4ccf6964..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonsAssociated.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/Requestor.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/Requestor.vue deleted file mode 100644 index 75a95e241..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/Requestor.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/index.js b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/index.js deleted file mode 100644 index 4335113f7..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import App from './App.vue'; -import { createApp } from 'vue'; - -const app = createApp({ - template: `` -}) -.component('app', App) -.mount('#accompanying-course'); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/store/.keep b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/store/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue new file mode 100644 index 000000000..8dd66d0f2 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js new file mode 100644 index 000000000..70b2ba56e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js @@ -0,0 +1,177 @@ +/* +* Endpoint v.2 chill_api_single_accompanying_course__entity +* method GET/HEAD, get AccompanyingCourse Instance +* +* @id integer - id of accompanyingCourse +*/ +const getAccompanyingCourse = (id) => { + const url = `/api/1.0/person/accompanying-course/${id}.json`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint v.2 chill_api_single_accompanying_course__entity +* method PATCH, patch AccompanyingCourse Instance +* +* @id integer - id of accompanyingCourse +* @body Object - dictionary with changes to post +*/ +const patchAccompanyingCourse = (id, body) => { + console.log('body', body); + const url = `/api/1.0/person/accompanying-course/${id}.json`; + return fetch(url, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(body) + }) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint to change 'DRAFT' step to 'CONFIRMED' +*/ +const confirmAccompanyingCourse = (id) => { + const url = `/api/1.0/person/accompanying-course/${id}/confirm.json` + return fetch(url, { + method: 'POST', + headers: {'Content-Type': 'application/json;charset=utf-8'} + }) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint +*/ +const getSocialIssues = () => { + const url = `/api/1.0/person/social-work/social-issue.json`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint v.2 chill_api_single_accompanying_course_participation, +* method POST/DELETE, add/close a participation to the accompanyingCourse +* +* @id integer - id of accompanyingCourse +* @payload integer - id of person +* @method string - POST or DELETE +*/ +const postParticipation = (id, payload, method) => { + const body = { type: payload.type, id: payload.id }; + const url = `/api/1.0/person/accompanying-course/${id}/participation.json`; + return fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(body) + }) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint v.2 chill_api_single_accompanying_course_requestor, +* method POST/DELETE, add/close a requestor to the accompanyingCourse +* +* @id integer - id of accompanyingCourse +* @payload object of type person|thirdparty +* @method string - POST or DELETE +*/ +const postRequestor = (id, payload, method) => { + //console.log('payload', payload); + const body = (payload)? { type: payload.type, id: payload.id } : {}; + console.log('body', body); + const url = `/api/1.0/person/accompanying-course/${id}/requestor.json`; + return fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(body) + }) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint v.2 chill_api_single_accompanying_course_resource, +* method POST/DELETE, add/remove a resource to the accompanyingCourse +* +* @id integer - id of accompanyingCourse +* @payload object of type person|thirdparty +* @method string - POST or DELETE +*/ +const postResource = (id, payload, method) => { + //console.log('payload', payload); + const body = { type: "accompanying_period_resource" }; + switch (method) { + case 'DELETE': + body['id'] = payload.id; + break; + default: + body['resource'] = { type: payload.type, id: payload.id }; + } + console.log('body', body); + const url = `/api/1.0/person/accompanying-course/${id}/resource.json`; + return fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(body) + }) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint to Add/remove SocialIssue +*/ +const postSocialIssue = (id, body, method) => { + //console.log('api body and method', body, method); + const url = `/api/1.0/person/accompanying-course/${id}/socialissue.json`; + return fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(body) + }) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +export { + getAccompanyingCourse, + patchAccompanyingCourse, + confirmAccompanyingCourse, + getSocialIssues, + postParticipation, + postRequestor, + postResource, + postSocialIssue +}; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue new file mode 100644 index 000000000..7a91e39c3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue new file mode 100644 index 000000000..7ed78355d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue new file mode 100644 index 000000000..67ef8c193 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue new file mode 100644 index 000000000..858d6d04e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue new file mode 100644 index 000000000..c2143b72a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue new file mode 100644 index 000000000..7c61b066c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/PersonItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/PersonItem.vue new file mode 100644 index 000000000..4fa2caf12 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/PersonItem.vue @@ -0,0 +1,58 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue new file mode 100644 index 000000000..b007bc17e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue new file mode 100644 index 000000000..c6783677a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue @@ -0,0 +1,122 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue new file mode 100644 index 000000000..596da7483 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue @@ -0,0 +1,85 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue new file mode 100644 index 000000000..ffec23ddf --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue @@ -0,0 +1,68 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue new file mode 100644 index 000000000..26bd160c1 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue @@ -0,0 +1,80 @@ + + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue new file mode 100644 index 000000000..117c4f09b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue new file mode 100644 index 000000000..413f216cc --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue new file mode 100644 index 000000000..a72902b25 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js new file mode 100644 index 000000000..66b6d6683 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js @@ -0,0 +1,23 @@ +import { createApp } from 'vue' +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' +import { appMessages } from './js/i18n' +import { initPromise } from './store' + +import App from './App.vue'; + +initPromise.then(store => { + + //console.log('store in create_store', store); + //console.log('store accompanyingCourse', store.state.accompanyingCourse); + + const i18n = _createI18n(appMessages); + + const app = createApp({ + template: ``, + }) + .use(store) + .use(i18n) + .component('app', App) + .mount('#accompanying-course'); + +}); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js new file mode 100644 index 000000000..242d48b8f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js @@ -0,0 +1,95 @@ +import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n' + +const appMessages = { + fr: { + course: { + id: "id", + title: { + draft: "Création d'un nouveau parcours", + active: "Modification du parcours" + }, + opening_date: "Date d'ouverture", + closing_date: "Date de clôture", + remark: "Commentaire", + closing_motive: "Motif de clôture", + user: "TMS", + flags: "Indicateurs", + status: "État", + step: { + draft: "Brouillon", + active: "En file active" + }, + open_at: "ouvert le ", + by: "par ", + emergency: "urgent", + confidential: "confidentiel", + regular: "régulier", + occasional: "ponctuel" + }, + persons_associated: { + title: "Usagers concernés", + counter: "Il n'y a pas encore d'usager | 1 usager | {count} usagers", + firstname: "Prénom", + lastname: "Nom", + startdate: "Date d'entrée", + enddate: "Date de sortie", + add_persons: "Ajouter des usagers", + }, + requestor: { + title: "Demandeur", + add_requestor: "Ajouter un demandeur", + is_anonymous: "Le demandeur est anonyme", + counter: "Il n'y a pas encore de demandeur", + type: "Type", + person_id: "id", + text: "Dénomination", + firstName: "Prénom", + lastName: "Nom", + birthdate: "Date de naissance", + center: "Centre", + phonenumber: "Téléphone", + mobilenumber: "Mobile", + altNames: "Autres noms", + address: "Adresse", + location: "Localité", + }, + social_issue: { + title: "Problématiques sociales", + label: "Choisir les problématiques sociales", + }, + referrer: { + title: "Référent du parcours", + label: "Vous pouvez choisir un TMS ou vous assigner directement comme référent", + placeholder: "Choisir un TMS", + assign_me: "M'assigner comme référent", + }, + resources: { + title: "Interlocuteurs privilégiés", + counter: "Il n'y a pas encore d'interlocuteur | 1 interlocuteur | {count} interlocuteurs", + text: "Dénomination", + description: "Description", + add_resources: "Ajouter des interlocuteurs", + }, + comment: { + title: "Observations", + label: "Ajout d'une note", + content: "Rédigez une première note...", + created_by: "créé par {0}, le {1}" + }, + confirm: { + title: "Confirmation", + text_draft: "Le parcours est actuellement à l'état de ", + text_active: "En validant cette étape, vous lui donnez le statut ", + sure: "Êtes-vous sûr ?", + sure_description: "Une fois le changement confirmé, il n'est plus possible de le remettre à l'état de brouillon !", + ok: "Confirmer le parcours" + }, + + } +}; + +Object.assign(appMessages.fr, personMessages.fr); + +export { + appMessages +}; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js new file mode 100644 index 000000000..c9121c5fd --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js @@ -0,0 +1,204 @@ +import 'es6-promise/auto'; +import { createStore } from 'vuex'; +import { getAccompanyingCourse, + patchAccompanyingCourse, + confirmAccompanyingCourse, + postParticipation, + postRequestor, + postResource, + postSocialIssue } from '../api'; + +const debug = process.env.NODE_ENV !== 'production'; +const id = window.accompanyingCourseId; + +let initPromise = getAccompanyingCourse(id) + .then(accompanying_course => new Promise((resolve, reject) => { + + const store = createStore({ + strict: debug, + modules: { + }, + state: { + accompanyingCourse: accompanying_course, + errorMsg: [] + }, + getters: { + }, + mutations: { + catchError(state, error) { + state.errorMsg.push(error); + }, + removeParticipation(state, participation) { + //console.log('### mutation: remove participation', participation.id); + state.accompanyingCourse.participations = state.accompanyingCourse.participations.filter(element => element !== participation); + }, + closeParticipation(state, { participation, payload }) { + //console.log('### mutation: close item', { participation, payload }); + // find row position and replace by closed participation + state.accompanyingCourse.participations.splice( + state.accompanyingCourse.participations.findIndex(element => element === payload), 1, participation + ); + }, + addParticipation(state, participation) { + //console.log('### mutation: add participation', participation); + state.accompanyingCourse.participations.push(participation); + }, + removeRequestor(state) { + //console.log('### mutation: removeRequestor'); + state.accompanyingCourse.requestor = null; + }, + addRequestor(state, requestor) { + //console.log('### mutation: addRequestor', requestor); + state.accompanyingCourse.requestor = requestor; + }, + requestorIsAnonymous(state, value) { + //console.log('### mutation: requestorIsAnonymous', value); + state.accompanyingCourse.requestorAnonymous = value; + }, + removeResource(state, resource) { + //console.log('### mutation: removeResource', resource); + state.accompanyingCourse.resources = state.accompanyingCourse.resources.filter(element => element !== resource); + }, + addResource(state, resource) { + //console.log('### mutation: addResource', resource); + state.accompanyingCourse.resources.push(resource); + }, + toggleIntensity(state, value) { + state.accompanyingCourse.intensity = value; + }, + toggleEmergency(state, value) { + //console.log('### mutation: toggleEmergency'); + state.accompanyingCourse.emergency = value; + }, + toggleConfidential(state, value) { + //console.log('### mutation: toggleConfidential'); + state.accompanyingCourse.confidential = value; + }, + postFirstComment(state, comment) { + //console.log('### mutation: postFirstComment', comment); + state.accompanyingCourse.initialComment = comment; + }, + updateSocialIssues(state, value) { + state.accompanyingCourse.socialIssues = value; + }, + confirmAccompanyingCourse(state, response) { + //console.log('### mutation: confirmAccompanyingCourse: response', response); + state.accompanyingCourse.step = response.step; + } + }, + actions: { + removeParticipation({ commit }, payload) { + commit('removeParticipation', payload); + // fetch DELETE request... + }, + closeParticipation({ commit }, payload) { + //console.log('## action: fetch delete participation: payload', payload); + postParticipation(id, payload.person, 'DELETE') + .then(participation => new Promise((resolve, reject) => { + commit('closeParticipation', { participation, payload }); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + addParticipation({ commit }, payload) { + //console.log('## action: fetch post participation (select item): payload', payload); + postParticipation(id, payload.result, 'POST') + .then(participation => new Promise((resolve, reject) => { + commit('addParticipation', participation); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + removeRequestor({ commit, dispatch }) { + //console.log('## action: fetch delete requestor'); + postRequestor(id, null, 'DELETE') + .then(requestor => new Promise((resolve, reject) => { + commit('removeRequestor'); + dispatch('requestorIsAnonymous', false); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + addRequestor({ commit }, payload) { + //console.log('## action: fetch post requestor: payload', payload); + postRequestor(id, payload.result, 'POST') + .then(requestor => new Promise((resolve, reject) => { + commit('addRequestor', requestor); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + requestorIsAnonymous({ commit }, payload) { + //console.log('## action: fetch patch AccompanyingCourse: payload', payload); + patchAccompanyingCourse(id, { type: "accompanying_period", requestorAnonymous: payload }) + .then(course => new Promise((resolve, reject) => { + commit('requestorIsAnonymous', course.requestorAnonymous) + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + removeResource({ commit }, payload) { + //console.log('## action: fetch postResource: payload', payload); + postResource(id, payload, 'DELETE') + .then(resource => new Promise((resolve, reject) => { + commit('removeResource', payload) // mieux un retour de l'objet ! + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + addResource({ commit }, payload) { + //console.log('## action: fetch postResource: payload', payload); + postResource(id, payload.result, 'POST') + .then(resource => new Promise((resolve, reject) => { + commit('addResource', resource) + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + toggleIntensity({ commit }, payload) { + //console.log(payload); + patchAccompanyingCourse(id, { type: "accompanying_period", intensity: payload }) + .then(course => new Promise((resolve, reject) => { + commit('toggleIntensity', course.intensity); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + toggleEmergency({ commit }, payload) { + patchAccompanyingCourse(id, { type: "accompanying_period", emergency: payload }) + .then(course => new Promise((resolve, reject) => { + commit('toggleEmergency', course.emergency); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + toggleConfidential({ commit }, payload) { + patchAccompanyingCourse(id, { type: "accompanying_period", confidential: payload }) + .then(course => new Promise((resolve, reject) => { + commit('toggleConfidential', course.confidential); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + postFirstComment({ commit }, payload) { + //console.log('## action: postFirstComment: payload', payload); + patchAccompanyingCourse(id, { type: "accompanying_period", initialComment: payload }) + .then(course => new Promise((resolve, reject) => { + commit('postFirstComment', course.initialComment); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + updateSocialIssues({ commit }, { payload, body, method }) { + //console.log('## action: payload', { payload, body, method }); + postSocialIssue(id, body, method) + .then(response => new Promise((resolve, reject) => { + //console.log('response', response); + commit('updateSocialIssues', payload); + resolve(); + })).catch((error) => { commit('catchError', error) }); + }, + confirmAccompanyingCourse({ commit }) { + //console.log('## action: confirmAccompanyingCourse'); + confirmAccompanyingCourse(id) + .then(response => new Promise((resolve, reject) => { + commit('confirmAccompanyingCourse', response); + console.log('fetch resolve'); // redirection with #top anchor + resolve(); + })).catch((error) => { commit('catchError', error) }); + } + } + }); + resolve(store); + })); + +export { initPromise }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js new file mode 100644 index 000000000..cf2404288 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js @@ -0,0 +1,46 @@ +/* +* Build query string with query and options +*/ +const parametersToString = ({ query, options }) => { + let types =''; + options.type.forEach(function(type) { + types += '&type[]=' + type; + }); + return 'q=' + query + types; +}; + +/* +* Endpoint chill_person_search +* method GET, get a list of persons +* +* @query string - the query to search for +*/ +const searchPersons = ({ query, options }) => { + let queryStr = parametersToString({ query, options }); + let url = `/fr/search.json?name=person_regular&${queryStr}`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint v.2 chill_main_search_global +* method GET, get a list of persons and thirdparty +* +* NOTE: this is a temporary WIP endpoint, return inconsistent random results +* @query string - the query to search for +*/ +const searchPersons_2 = ({ query, options }) => { + let queryStr = parametersToString({ query, options }); + let url = `/api/1.0/search.json?${queryStr}`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + + +export { searchPersons, searchPersons_2 }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue new file mode 100644 index 000000000..a37f93e7e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue new file mode 100644 index 000000000..3846f7b39 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue new file mode 100644 index 000000000..af5b13b16 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue new file mode 100644 index 000000000..37cee7852 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js new file mode 100644 index 000000000..e92a81e62 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js @@ -0,0 +1,26 @@ +const personMessages = { + fr: { + add_persons: { + title: "Ajouter des usagers", + suggested_counter: "Pas de résultats | 1 résultat | {count} résultats", + selected_counter: " 1 sélectionné | {count} sélectionnés", + search_some_persons: "Rechercher des personnes..", + }, + item: { + type_person: "Usager", + type_user: "TMS", + type_thirdparty: "Tiers", + type_household: "Ménage" + }, + person: { + firstname: "Prénom", + lastname: "Nom", + born: "né le ", + }, + error_only_one_person: "Une seule personne peut être sélectionnée !" + } +}; + +export { + personMessages +}; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/banner.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/banner.html.twig index 6ddb46246..3c1d0dadc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/banner.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/banner.html.twig @@ -2,39 +2,24 @@
-
{% set title = title %} +
{% set title = title %}

- {{ 'Accompanying Course'|trans }}{# ou défini en amont - {{ title|default('Accompanying Course'|trans)|raw }} #} - (n°{{ accompanyingCourse.id }}) + {{ 'Accompanying Course'|trans }} + (n°{{ accompanyingCourse.id }})

-
-
    -
  • ponctuel régulier
  • -
  • ouvert
  • -
  • en file active
  • -
  • urgent
  • -
-
+ -
-

- ouvert le 11 avril 2019
- par Soline Maillet | SIPAS -

-
+
-
Problématiques sociales
-
_
-
_
+
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig index 82f7102fd..e12037cd2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig @@ -6,21 +6,23 @@ {% block content %} -

{{ block('title') }}

+ {% if 'DRAFT' == accompanyingCourse.step %} +
+ + {{ 'This accompanying course is still a draft'|trans }} + + {{ 'Edit & activate accompanying course'|trans }} + + +
+ {% endif %} -
-{{ accompanyingCourse.id }}
-{{ accompanyingCourse.openingDate|format_date('short') }}
-{{ accompanyingCourse.closingDate|format_date('short') }}
-{{ accompanyingCourse.closingMotive|chill_entity_render_box }}
-{{ accompanyingCourse.remark|raw }}
-{{ accompanyingCourse.user }}
-usagers:
-{% for p in accompanyingCourse.participations %}
-    {{ p.person.id }} | {{ p.person.fullnamecanonical }} | {{ p.startdate|format_date('short') }} | {{ p.enddate|format_date('short') }}
-{% endfor %}
-    
+

{{ 'Associated peoples'|trans }}

- {{ dump() }} +

{{ 'Resources'|trans }}

+ +

{{ 'Social actions'|trans }}

+ +

{{ 'Last events on accompanying course'|trans }}

{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/show.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/show.html.twig index a33b65cff..23ffb5864 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/show.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/show.html.twig @@ -1,19 +1,22 @@ {% extends 'ChillPersonBundle:AccompanyingCourse:layout.html.twig' %} +{% set title = 'DRAFT' == accompanyingCourse.step ? 'New accompanying course' : 'Edit accompanying course' %} + {% block title %} - {{ 'Edit Accompanying Course'|trans }} + {{ title|trans }} {% endblock %} {% block content %} +
{# <== insert accompanyingCourse vue component #} +{% endblock %} -

{{ block('title') }}

- -
- - {{ encore_entry_script_tags('accompanying_course') }} +{% block css %} + {{ encore_entry_link_tags('accompanying_course') }} +{% endblock %} +{% block js %} - + {{ encore_entry_script_tags('accompanying_course') }} {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Address/new.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Address/new.html.twig index 70cc51e53..17591f86d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Address/new.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Address/new.html.twig @@ -49,4 +49,22 @@ {{ form_end(form) }} + + NEW FORM + + {% block content %} +

{{ block('title') }}

+
+ {% endblock %} + + {% block stylesheets %} + + {% endblock %} + + {% block js %} + {{ encore_entry_script_tags('address') }} + {% endblock %} + + + {% endblock personcontent %} diff --git a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php index 956f9a4d2..1c564aa11 100644 --- a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php +++ b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php @@ -22,12 +22,13 @@ use Chill\PersonBundle\Entity\PersonNotDuplicate; use Doctrine\ORM\EntityManagerInterface; use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\PersonBundle\Repository\PersonNotDuplicateRepository; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; use Chill\PersonBundle\Security\Authorization\PersonVoter; /** - * + * * * @author Julien Fastré */ @@ -41,20 +42,20 @@ class SimilarPersonMatcher * @var EntityManagerInterface */ protected $em; - + /** * @var AuthorizationHelper */ protected $authorizationHelper; - + /** - * @var TokenStorageInterface + * @var TokenStorageInterface */ protected $tokenStorage; - + public function __construct( - EntityManagerInterface $em, - AuthorizationHelper $authorizationHelper, + EntityManagerInterface $em, + AuthorizationHelper $authorizationHelper, TokenStorageInterface $tokenStorage ) { $this->em = $em; @@ -62,7 +63,7 @@ class SimilarPersonMatcher $this->tokenStorage = $tokenStorage; } - public function matchPerson(Person $person, $precision = 0.15, $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY) + public function matchPerson(Person $person, PersonNotDuplicateRepository $personNotDuplicateRepository, $precision = 0.15, $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY) { $centers = $this->authorizationHelper->getReachableCenters( $this->tokenStorage->getToken()->getUser(), @@ -77,8 +78,7 @@ class SimilarPersonMatcher . ' AND p.id != :personId ' ; - $notDuplicatePersons = $this->em->getRepository(PersonNotDuplicate::class) - ->findNotDuplicatePerson($person); + $notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person); if (count($notDuplicatePersons)) { $dql .= ' AND p.id not in (:notDuplicatePersons)'; diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php index ca92c6d7c..2801cb8f7 100644 --- a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php +++ b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php @@ -38,6 +38,9 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRole } // TODO take scopes into account + if (count($subject->getPersons()) === 0) { + return true; + } foreach ($subject->getPersons() as $person) { // give access as soon as on center is reachable if ($this->helper->userHasAccess($token->getUser(), $person->getCenter(), $attribute)) { diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodNormalizer.php deleted file mode 100644 index e953b5c49..000000000 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodNormalizer.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -namespace Chill\PersonBundle\Serializer\Normalizer; - -use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - - -class AccompanyingPeriodNormalizer implements NormalizerInterface, NormalizerAwareInterface { - - protected ?NormalizerInterface $normalizer = null; - - public function normalize($period, string $format = null, array $context = array()) - { - /** @var AccompanyingPeriod $period */ - return [ - 'id' => $period->getId(), - 'openingDate' => $this->normalizer->normalize($period->getOpeningDate(), $format), - 'closingDate' => $this->normalizer->normalize($period->getClosingDate(), $format), - 'remark' => $period->getRemark(), - 'participations' => $this->normalizer->normalize($period->getParticipations(), $format), - 'closingMotive' => $this->normalizer->normalize($period->getClosingMotive(), $format), - 'user' => $this->normalizer->normalize($period->getUser(), $format), - 'step' => $period->getStep(), - 'origin' => $this->normalizer->normalize($period->getOrigin(), $format), - 'intensity' => $period->getIntensity(), - 'emergency' => $period->isEmergency(), - 'confidential' => $period->isConfidential() - ]; - } - - public function supportsNormalization($data, string $format = null): bool - { - return $data instanceof AccompanyingPeriod; - } - - public function setNormalizer(NormalizerInterface $normalizer) - { - $this->normalizer = $normalizer; - } -} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodParticipationNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodParticipationNormalizer.php index 59e430827..a1321b3ef 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodParticipationNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodParticipationNormalizer.php @@ -41,6 +41,7 @@ class AccompanyingPeriodParticipationNormalizer implements NormalizerInterface, public function supportsNormalization($data, string $format = null): bool { + return false; return $data instanceof AccompanyingPeriodParticipation; } diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodResourceNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodResourceNormalizer.php new file mode 100644 index 000000000..b5fca8d59 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodResourceNormalizer.php @@ -0,0 +1,85 @@ +repository = $repository; + } + + public function denormalize($data, string $type, string $format = null, array $context = []) + { + $resource = $this->extractObjectToPopulate($type, $context); + + if ('accompanying_period_resource' !== ($data['type'] ?? NULL)) { + throw new Exception\InvalidArgumentException("the key type must be present in data and set to 'accompanying_period_resource'"); + } + + if ($resource === NULL && \array_key_exists('id', $data)) { + $resource = $this->repository->find($data['id']); + + if (NULL === $resource) { + throw new Exception\UnexpectedValueException(sprintf("the resource with". + "id %d is not found", $data['id'])); + } + + // if resource found, available only for read-only + if (count($data) > 2) { + unset($data['id']); + unset($data['type']); + throw new Exception\ExtraAttributesException($data); + } + } + + if ($resource === NULL) { + $resource = new Resource(); + } + + if (\array_key_exists('resource', $data)) { + $res = $this->denormalizer->denormalize( + $data['resource'], + DiscriminatedObjectDenormalizer::TYPE, + $format, + \array_merge( + $context, + [ + DiscriminatedObjectDenormalizer::ALLOWED_TYPES => + [ + Person::class, ThirdParty::class + ] + ] + ) + ); + + $resource->setResource($res); + } + + return $resource; + } + + + public function supportsDenormalization($data, string $type, string $format = null) + { + return $type === Resource::class; + } +} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php index d88d27ddc..98edcec14 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php @@ -18,14 +18,20 @@ */ namespace Chill\PersonBundle\Serializer\Normalizer; +use Chill\MainBundle\Entity\Center; use Chill\PersonBundle\Entity\Person; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Chill\PersonBundle\Repository\PersonRepository; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; - +use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait; /** * Serialize a Person entity @@ -34,17 +40,22 @@ use Symfony\Component\Serializer\Exception\UnexpectedValueException; class PersonNormalizer implements NormalizerInterface, NormalizerAwareInterface, - DenormalizerInterface + DenormalizerInterface, + DenormalizerAwareInterface { + private ChillEntityRenderExtension $render; - protected NormalizerInterface $normalizer; + private PersonRepository $repository; - protected PersonRepository $repository; + use NormalizerAwareTrait; - public const GET_PERSON = 'get_person'; + use ObjectToPopulateTrait; - public function __construct(PersonRepository $repository) + use DenormalizerAwareTrait; + + public function __construct(ChillEntityRenderExtension $render, PersonRepository $repository) { + $this->render = $render; $this->repository = $repository; } @@ -52,44 +63,82 @@ class PersonNormalizer implements { /** @var Person $person */ return [ + 'type' => 'person', 'id' => $person->getId(), + 'text' => $this->render->renderString($person), 'firstName' => $person->getFirstName(), 'lastName' => $person->getLastName(), 'birthdate' => $this->normalizer->normalize($person->getBirthdate()), - 'center' => $this->normalizer->normalize($person->getCenter()) + 'center' => $this->normalizer->normalize($person->getCenter()), + 'phonenumber' => $person->getPhonenumber(), + 'mobilenumber' => $person->getMobilenumber(), + 'altNames' => $this->normalizeAltNames($person->getAltNames()), + 'gender' => $person->getGender(), + 'gender_numeric' => $person->getGenderNumeric(), ]; } - public function denormalize($data, string $type, string $format = null, array $context = []): Person + protected function normalizeAltNames($altNames): array { - if ($context[self::GET_PERSON] ?? true) { - $id = $data['id'] ?? null; - if (NULL === $id) { - throw new RuntimeException("missing id into person object"); - } - } - /** var Person $person */ - $person = $this->repository->findOneById($id); + $r = []; - if (NULL === $person) { - return UnexpectedValueException("person id not found"); + foreach ($altNames as $n) { + $r[] = [ 'key' => $n->getKey(), 'label' => $n->getLabel() ]; } - return $person; + return $r; } + public function supportsNormalization($data, string $format = null): bool { return $data instanceof Person; } - public function supportsDenormalization($data, string $type, ?string $format = NULL): bool + public function denormalize($data, string $type, string $format = null, array $context = []) { - return Person::class === $type; + $person = $this->extractObjectToPopulate($type, $context); + + if (\array_key_exists('id', $data)) { + $person = $this->repository->find($data['id']); + + if (null === $person) { + throw new UnexpectedValueException("The person with id \"{$data['id']}\" does ". + "not exists"); + } + // currently, not allowed to update a person through api + // if instantiated with id + return $person; + } + + if (null === $person) { + $person = new Person(); + } + + foreach (['firstName', 'lastName', 'phonenumber', 'mobilenumber', 'gender'] + as $item) { + if (\array_key_exists($item, $data)) { + $person->{'set'.\ucfirst($item)}($data[$item]); + } + } + + foreach ([ + 'birthdate' => \DateTime::class, + 'center' => Center::class + ] as $item => $class) { + if (\array_key_exists($item, $data)) { + $object = $this->denormalizer->denormalize($data[$item], $class, $format, $context); + if ($object instanceof $class) { + $person->{'set'.\ucfirst($item)}($object); + } + } + } + + return $person; } - public function setNormalizer(NormalizerInterface $normalizer) + public function supportsDenormalization($data, string $type, string $format = null) { - $this->normalizer = $normalizer; + return $type === Person::class && ($data['type'] ?? NULL) === 'person'; } } diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php new file mode 100644 index 000000000..e78febcde --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php @@ -0,0 +1,43 @@ +render = $render; + } + + + public function normalize($socialIssue, string $format = null, array $context = []) + { + /** @var SocialIssue $socialIssue */ + return [ + 'type' => 'social_issue', + 'id' => $socialIssue->getId(), + 'parent_id' => $socialIssue->hasParent() ? $socialIssue->getParent()->getId() : null, + 'children_ids' => $socialIssue->getChildren()->map(function (SocialIssue $si) { return $si->getId(); }), + 'title' => $socialIssue->getTitle(), + 'text' => $this->render->renderString($socialIssue, []) + ]; + } + + public function supportsNormalization($data, string $format = null): bool + { + return $data instanceof SocialIssue; + } +} diff --git a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialIssueRender.php b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialIssueRender.php new file mode 100644 index 000000000..4a7ed0cb5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialIssueRender.php @@ -0,0 +1,50 @@ + ' > ', + ]; + + public function __construct(TranslatableStringHelper $translatableStringHelper) + { + $this->translatableStringHelper = $translatableStringHelper; + } + + public function supports($entity, array $options): bool + { + return $entity instanceof SocialIssueRender; + } + + public function renderString($socialIssue, array $options): string + { + /** @var $socialIssue SocialIssue */ + $options = \array_merge(self::DEFAULT_ARGS, $options); + + $str = $this->translatableStringHelper->localize($socialIssue->getTitle()); + + while ($socialIssue->hasParent()) { + $socialIssue = $socialIssue->getParent(); + $str .= $options[self::SEPARATOR_KEY].$this->translatableStringHelper->localize( + $socialIssue->getTitle() + ); + } + + return $str; + } + + public function renderBox($entity, array $options): string + { + return "renderBox not implemented for social issue"; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php index 61b5307a4..a41e570a5 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php @@ -27,11 +27,14 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Entity\SocialWork\SocialIssue; +use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\Center; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; +use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; /** * Test api for AccompanyingCourseControllerTest @@ -40,6 +43,10 @@ class AccompanyingCourseApiControllerTest extends WebTestCase { protected static EntityManagerInterface $em; + protected ?int $personId = NULL; + + protected ?AccompanyingPeriod $period = NULL; + /** * Setup before the first test of this class (see phpunit doc) */ @@ -85,6 +92,310 @@ class AccompanyingCourseApiControllerTest extends WebTestCase $this->assertEquals(404, $response->getStatusCode(), "Test that the response of rest api has a status code 'not found' (404)"); } + /** + * + * @dataProvider dataGenerateRandomAccompanyingCourseWithSocialIssue + */ + public function testAccompanyingCourseAddRemoveSocialIssue(AccompanyingPeriod $period, SocialIssue $si) + { + $this->client->request( + Request::METHOD_POST, + sprintf('/api/1.0/person/accompanying-course/%d/socialissue.json', $period->getId()), + [], + [], + [], + \json_encode([ 'type' => 'social_issue', 'id' => $si->getId() ]) + ); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + + $data = \json_decode($this->client->getResponse()->getContent(), true); + $this->assertArrayHasKey('id', $data); + $this->assertArrayHasKey('type', $data); + $this->assertEquals('social_issue', $data['type']); + + + $this->client->request( + Request::METHOD_DELETE, + sprintf('/api/1.0/person/accompanying-course/%d/socialissue.json', $period->getId()), + [], + [], + [], + \json_encode([ 'type' => 'social_issue', 'id' => $si->getId() ]) + ); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + } + + /** + * @dataProvider dataGenerateRandomRequestorValidData + */ + public function testCommentWithValidData(AccompanyingPeriod $period, $personId, $thirdPartyId) + { + $em = self::$container->get(EntityManagerInterface::class); + + $this->client->request( + Request::METHOD_POST, + sprintf('/api/1.0/person/accompanying-course/%d/comment.json', $period->getId()), + [], // parameters + [], // files + [], // server parameters + \json_encode([ 'type' => 'accompanying_period_comment', 'content' => "this is a text"]) + ); + $response = $this->client->getResponse(); + $data = \json_decode($response->getContent(), true); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('id', $data); + + $this->client->request( + Request::METHOD_DELETE, + sprintf('/api/1.0/person/accompanying-course/%d/comment.json', $period->getId()), + [], // parameters + [], // files + [], // server parameters + \json_encode([ 'type' => 'accompanying_period_comment', 'id' => $data['id']]) + ); + $response = $this->client->getResponse(); + $data = \json_decode($response->getContent(), true); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @dataProvider dataGenerateRandomRequestorValidData + */ + public function testResourceWithValidData(AccompanyingPeriod $period, $personId, $thirdPartyId) + { + $em = self::$container->get(EntityManagerInterface::class); + + // post a person + $this->client->request( + Request::METHOD_POST, + sprintf('/api/1.0/person/accompanying-course/%d/resource.json', $period->getId()), + [], // parameters + [], // files + [], // server parameters + \json_encode([ 'type' => 'accompanying_period_resource', 'resource' => [ 'type' => 'person', 'id' => $personId ]]) + ); + $response = $this->client->getResponse(); + $data = \json_decode($response->getContent(), true); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('id', $data); + $this->assertEquals($personId, $data['resource']['id']); + + // check into database + $resource = $em->getRepository(Resource::class) + ->find($data['id']); + $this->assertInstanceOf(Resource::class, $resource); + $this->assertInstanceOf(Person::class, $resource->getResource()); + $this->assertEquals($personId, $resource->getResource()->getId()); + + // remove the resource + $this->client->request( + Request::METHOD_DELETE, + sprintf('/api/1.0/person/accompanying-course/%d/requestor.json', $period->getId()), + [], + [], + [], //server + \json_encode([ 'type' => 'accompanying_period_resource', 'id' => $resource->getId()]) + ); + $response = $this->client->getResponse(); + $this->assertEquals(200, $response->getStatusCode()); + + // post a third party + $this->client->request( + Request::METHOD_POST, + sprintf('/api/1.0/person/accompanying-course/%d/resource.json', $period->getId()), + [], // parameters + [], // files + [], // server parameters + \json_encode([ 'type' => 'accompanying_period_resource', 'resource' => [ 'type' => 'thirdparty', 'id' => $thirdPartyId ]]) + ); + $response = $this->client->getResponse(); + $data = \json_decode($response->getContent(), true); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('id', $data); + $this->assertEquals($thirdPartyId, $data['resource']['id']); + + // check into database + $resource = $em->getRepository(Resource::class) + ->find($data['id']); + $this->assertInstanceOf(Resource::class, $resource); + $this->assertInstanceOf(ThirdParty::class, $resource->getResource()); + $this->assertEquals($thirdPartyId, $resource->getResource()->getId()); + + // remove the resource + $this->client->request( + Request::METHOD_DELETE, + sprintf('/api/1.0/person/accompanying-course/%d/requestor.json', $period->getId()), + [], + [], + [], //server + \json_encode([ 'type' => 'accompanying_period_resource', 'id' => $resource->getId()]) + ); + $response = $this->client->getResponse(); + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @dataProvider dataGenerateRandomRequestorValidData + */ + public function testRequestorWithValidData(AccompanyingPeriod $period, $personId, $thirdPartyId) + { + $em = self::$container->get(EntityManagerInterface::class); + + // post a person + $this->client->request( + Request::METHOD_POST, + sprintf('/api/1.0/person/accompanying-course/%d/requestor.json', $period->getId()), + [], // parameters + [], // files + [], // server parameters + \json_encode([ 'type' => 'person', 'id' => $personId ]) + ); + $response = $this->client->getResponse(); + $data = \json_decode($response->getContent(), true); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('id', $data); + $this->assertEquals($personId, $data['id']); + + // check into database + $period = $em->getRepository(AccompanyingPeriod::class) + ->find($period->getId()); + $this->assertInstanceOf(Person::class, $period->getRequestor()); + $this->assertEquals($personId, $period->getRequestor()->getId()); + + // post a third party + $this->client->request( + Request::METHOD_POST, + sprintf('/api/1.0/person/accompanying-course/%d/requestor.json', $period->getId()), + [], // parameters + [], // files + [], // server parameters + \json_encode([ 'type' => 'thirdparty', 'id' => $thirdPartyId ]) + ); + $response = $this->client->getResponse(); + $data = \json_decode($response->getContent(), true); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('id', $data); + $this->assertEquals($thirdPartyId, $data['id']); + + // check into database + $period = $em->getRepository(AccompanyingPeriod::class) + ->find($period->getId()); + $this->assertInstanceOf(ThirdParty::class, $period->getRequestor()); + $this->assertEquals($thirdPartyId, $period->getRequestor()->getId()); + + // remove the requestor + $this->client->request( + Request::METHOD_DELETE, + sprintf('/api/1.0/person/accompanying-course/%d/requestor.json', $period->getId()) + ); + $response = $this->client->getResponse(); + $this->assertEquals(200, $response->getStatusCode()); + + // check into database + $period = $em->getRepository(AccompanyingPeriod::class) + ->find($period->getId()); + $em->refresh($period); + $this->assertNull($period->getRequestor()); + } + + /** + * @dataProvider dataGenerateNewAccompanyingCourse + */ + public function testConfirm(AccompanyingPeriod $period) + { + $this->client->request( + Request::METHOD_POST, + sprintf('/api/1.0/person/accompanying-course/%d/confirm.json', $period->getId()) + ); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + + // add period to remove it in tear down + $this->period = $period; + } + + /** + * + * @dataProvider dataGenerateRandomAccompanyingCourse + */ + public function testAccompanyingPeriodPatch(int $personId, AccompanyingPeriod $period) + { + $initialValueEmergency = $period->isEmergency(); + $em = self::$container->get(EntityManagerInterface::class); + + $this->client->request( + Request::METHOD_PATCH, + sprintf('/api/1.0/person/accompanying-course/%d.json', $period->getId()), + [], // parameters + [], // files + [], // server parameters + \json_encode([ 'type' => 'accompanying_period', 'emergency' => !$initialValueEmergency ]) + ); + $response = $this->client->getResponse(); + + $this->assertEquals(200, $response->getStatusCode()); + $period = $em->getRepository(AccompanyingPeriod::class) + ->find($period->getId()); + $em->refresh($period); + $this->assertEquals(!$initialValueEmergency, $period->isEmergency()); + + // restore the initial valud + $period->setEmergency($initialValueEmergency); + $em->flush(); + } + + public function dataGenerateRandomRequestorValidData(): \Iterator + { + $dataLength = 2; + $maxResults = 100; + + static::bootKernel(); + $em = static::$container->get(EntityManagerInterface::class); + $center = $em->getRepository(Center::class) + ->findOneBy(array('name' => 'Center A')); + + $personIds = $em->createQuery("SELECT p.id FROM ". + Person::class." p ". + " WHERE p.center = :center") + ->setParameter('center', $center) + ->setMaxResults($maxResults) + ->getScalarResult(); + + // create a random order + shuffle($personIds); + + $thirdPartyIds = $em->createQuery("SELECT t.id FROM ". + ThirdParty::class." t ") + ->setMaxResults($maxResults) + ->getScalarResult(); + + // create a random order + shuffle($thirdPartyIds); + + $i = 0; + while ($i <= $dataLength) { + $person = $em->getRepository(Person::class) + ->find(\array_pop($personIds)['id']); + + if (count($person->getAccompanyingPeriods()) === 0) { + continue; + } + + $period = $person->getAccompanyingPeriods()[0]; + + yield [$period, \array_pop($personIds)['id'], \array_pop($thirdPartyIds)['id'] ]; + $i++; + } + } + /** * * @dataProvider dataGenerateRandomAccompanyingCourse @@ -97,7 +408,7 @@ class AccompanyingCourseApiControllerTest extends WebTestCase [], // parameters [], // files [], // server parameters - \json_encode([ 'id' => $personId ]) + \json_encode([ 'type' => 'person', 'id' => $personId ]) ); $response = $this->client->getResponse(); $data = \json_decode($response->getContent(), true); @@ -128,7 +439,7 @@ class AccompanyingCourseApiControllerTest extends WebTestCase [], // parameters [], // files [], // server parameters - \json_encode([ 'id' => $personId ]) + \json_encode([ 'type' => 'person', 'id' => $personId ]) ); $response = $this->client->getResponse(); $data = \json_decode($response->getContent(), true); @@ -148,25 +459,79 @@ class AccompanyingCourseApiControllerTest extends WebTestCase protected function tearDown() { - // remove participation created during test 'testAccompanyingCourseAddParticipation' - // and if the test could not remove it - - $testAddParticipationName = 'testAccompanyingCourseAddParticipation'; - - if ($testAddParticipationName !== \substr($this->getName(), 0, \strlen($testAddParticipationName))) { - return; - } - $em = static::$container->get(EntityManagerInterface::class); - $participation = $em - ->getRepository(AccompanyingPeriodParticipation::class) - ->findOneBy(['person' => $this->personId, 'accompanyingPeriod' => $this->period]) - ; + // remove participation if set + if ($this->personId && $this->period) { + $participation = $em + ->getRepository(AccompanyingPeriodParticipation::class) + ->findOneBy(['person' => $this->personId, 'accompanyingPeriod' => $this->period]) + ; - if (NULL !== $participation) { - $em->remove($participation); - $em->flush(); + if (NULL !== $participation) { + $em->remove($participation); + $em->flush(); + } + $this->personId = NULL; + $this->period = NULL; + } elseif ($this->period) { + $period = $em + ->getRepository(AccompanyingPeriod::class) + ->find($this->period->getId()) ; + + if ($period !== NULL) { + $em->remove($period); + $em->flush(); + } + + $this->period = null; + } + } + + public function dataGenerateRandomAccompanyingCourseWithSocialIssue() + { + // note about max result for person query, and maxGenerated: + // + // in the final loop, an id is popped out of the personIds array twice: + // + // * one for getting the person, which will in turn provide his accompanying period; + // * one for getting the personId to populate to the data manager + // + // Ensure to keep always $maxGenerated to the double of $maxResults. x8 is a good compromize :) + $maxGenerated = 3; + $maxResults = $maxGenerated * 8; + + static::bootKernel(); + $em = static::$container->get(EntityManagerInterface::class); + $center = $em->getRepository(Center::class) + ->findOneBy(array('name' => 'Center A')); + + $personIds = $em->createQuery("SELECT p.id FROM ". + Person::class." p ". + " WHERE p.center = :center") + ->setParameter('center', $center) + ->setMaxResults($maxResults) + ->getScalarResult(); + + // create a random order + shuffle($personIds); + + $socialIssues = $em->createQuery("SELECT s FROM ". + SocialIssue::class." s ") + ->setMaxResults(10) + ->getResult(); + + $nbGenerated = 0; + while ($nbGenerated < $maxGenerated) { + $id = \array_pop($personIds)["id"]; + + $person = $em->getRepository(Person::class) + ->find($id); + $periods = $person->getAccompanyingPeriods(); + + yield [$periods[\array_rand($periods)], $socialIssues[\array_rand($socialIssues)] ]; + + $nbGenerated++; } } @@ -211,4 +576,39 @@ class AccompanyingCourseApiControllerTest extends WebTestCase $nbGenerated++; } } + + public function dataGenerateNewAccompanyingCourse() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $period = new AccompanyingPeriod(new \DateTime('1 week ago')); + $user = $em->getRepository(User::class) + ->findOneByUsernameCanonical('center a_social'); + $period->setCreatedBy($user); + //$period->setCreatedAt(new \DateTime('yesterday')); + + $center = $em->getRepository(Center::class) + ->findOneBy(array('name' => 'Center A')); + + $personIds = $em->createQuery("SELECT p.id FROM ". + Person::class." p ". + " WHERE p.center = :center") + ->setParameter('center', $center) + ->setMaxResults(100) + ->getScalarResult(); + + // create a random order + shuffle($personIds); + + for ($i = 0; $i<2; $i++) { + $person = $em->getRepository(Person::class)->find(\array_pop($personIds)); + $period->addPerson($person); + } + + $em->persist($period); + $em->flush(); + + yield [ $period ]; + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php new file mode 100644 index 000000000..0143a70ed --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php @@ -0,0 +1,88 @@ +client = $this->getClientAuthenticated(); + } + + public function testNewWithoutUsers() + { + $this->client->request('GET', '/fr/person/parcours/new'); + + $this->assertResponseRedirects(); + $location = $this->client->getResponse()->headers->get('Location'); + + $this->assertEquals(1, \preg_match("|^\/[^\/]+\/parcours/([\d]+)/show$|", $location)); + + } + + /** + * @dataProvider dataGenerateRandomUsers + */ + public function testWithNewUsers($personId0, $personId1) + { + $this->client->request('GET', '/fr/person/parcours/new', [ + 'person_id' => [ + $personId0, + $personId1 + ] + ]); + + $this->assertResponseRedirects(); + $location = $this->client->getResponse()->headers->get('Location'); + $matches = []; + + $this->assertEquals(1, \preg_match("|^\/[^\/]+\/parcours/([\d]+)/show$|", $location, $matches)); + $id = $matches[1]; + + $period = self::$container->get(EntityManagerInterface::class) + ->getRepository(AccompanyingPeriod::class) + ->find($id); + + $this->assertNotNull($period); + + $this->assertEquals(2, count($period->getParticipations())); + } + + public function dataGenerateRandomUsers(): \Iterator + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $period = new AccompanyingPeriod(new \DateTime('1 week ago')); + $user = $em->getRepository(User::class) + ->findOneByUsernameCanonical('center a_social'); + $period->setCreatedBy($user); + //$period->setCreatedAt(new \DateTime('yesterday')); + + $center = $em->getRepository(Center::class) + ->findOneBy(array('name' => 'Center A')); + + $personIds = $em->createQuery("SELECT p.id FROM ". + Person::class." p ". + " WHERE p.center = :center") + ->setParameter('center', $center) + ->setMaxResults(100) + ->getScalarResult(); + + yield [ \array_pop($personIds), \array_pop($personIds) ]; + } + + +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonApiControllerTest.php new file mode 100644 index 000000000..d261f4294 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonApiControllerTest.php @@ -0,0 +1,83 @@ +getClientAuthenticated(); + + $client->request(Request::METHOD_GET, "/api/1.0/person/person/{$personId}.json"); + $response = $client->getResponse(); + + $this->assertEquals(403, $response->getStatusCode()); + } + + /** + * @dataProvider dataGetPersonFromCenterA + */ + public function testPersonGet($personId): void + { + $client = $this->getClientAuthenticated(); + + $client->request(Request::METHOD_GET, "/api/1.0/person/person/{$personId}.json"); + $response = $client->getResponse(); + + $this->assertResponseIsSuccessful(); + + $data = \json_decode($client->getResponse()->getContent(), true); + + $this->assertArrayHasKey('type', $data); + $this->assertArrayHasKey('id', $data); + $this->assertEquals('person', $data['type']); + $this->assertEquals($personId, $data['id']); + } + + public function dataGetPersonFromCenterA(): \Iterator + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + $personIds= $em->createQuery("SELECT p.id FROM ".Person::class." p ". + "JOIN p.center c ". + "WHERE c.name = :center") + ->setParameter('center', 'Center A') + ->setMaxResults(100) + ->getScalarResult() + ; + + \shuffle($personIds); + + yield \array_pop($personIds); + yield \array_pop($personIds); + } + + public function dataGetPersonFromCenterB(): \Iterator + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + $personIds= $em->createQuery("SELECT p.id FROM ".Person::class." p ". + "JOIN p.center c ". + "WHERE c.name = :center") + ->setParameter('center', 'Center B') + ->setMaxResults(100) + ->getScalarResult() + ; + + \shuffle($personIds); + + yield \array_pop($personIds); + yield \array_pop($personIds); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerCreateTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerCreateTest.php index c49c0ea97..d1381c241 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerCreateTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerCreateTest.php @@ -25,6 +25,7 @@ namespace Chill\PersonBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\DomCrawler\Form; use Chill\MainBundle\Test\PrepareClientTrait; +use \Symfony\Component\BrowserKit\Client; /** * Test creation and deletion for persons @@ -32,7 +33,9 @@ use Chill\MainBundle\Test\PrepareClientTrait; class PersonControllerCreateTest extends WebTestCase { use PrepareClientTrait; - + + private Client $client; + const FIRSTNAME_INPUT = 'chill_personbundle_person_creation[firstName]'; const LASTNAME_INPUT = "chill_personbundle_person_creation[lastName]"; const GENDER_INPUT = "chill_personbundle_person_creation[gender]"; @@ -42,18 +45,20 @@ class PersonControllerCreateTest extends WebTestCase const LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta.Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq."; - public function setUp() + public function setUp(): void { - $this->client = $this::getClientAuthenticated(); + $this->client = $this->getClientAuthenticated(); } /** * * @param Form $creationForm */ - private function fillAValidCreationForm(Form &$creationForm, - $firstname = 'God', $lastname = 'Jesus') - { + private function fillAValidCreationForm( + Form &$creationForm, + string $firstname = 'God', + string $lastname = 'Jesus' + ) { $creationForm->get(self::FIRSTNAME_INPUT)->setValue($firstname); $creationForm->get(self::LASTNAME_INPUT)->setValue($lastname); $creationForm->get(self::GENDER_INPUT)->select("man"); diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php index 1292ecbeb..e6d0c207f 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php @@ -11,7 +11,7 @@ class PersonDuplicateControllerViewTest extends WebTestCase { static::bootKernel(); - $this->em = static::$kernel->getContainer() + $this->em = static::$container ->get('doctrine.orm.entity_manager'); $center = $this->em->getRepository('ChillMainBundle:Center') diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/SocialIssueApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/SocialIssueApiControllerTest.php new file mode 100644 index 000000000..36d5dca37 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/SocialIssueApiControllerTest.php @@ -0,0 +1,55 @@ +getClientAuthenticated(); + $client->request(Request::METHOD_GET, '/api/1.0/person/social-work/social-issue.json'); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $data = \json_decode($client->getResponse()->getContent(), true); + + $this->assertGreaterThan(0, $data['count']); + $this->assertGreaterThan(0, count($data['results'])); + + return $data; + } + + /** + * @depends testList + */ + public function testItem(array $data): void + { + $socialIssues = $data['results']; + shuffle($socialIssues); + $socialIssue = \array_pop($socialIssues); + + + $client = $this->getClientAuthenticated(); + $client->request(Request::METHOD_GET, sprintf('/api/1.0/person/social-work/social-issue/%d.json', $socialIssue['id'])); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $data = \json_decode($client->getResponse()->getContent(), true); + + $this->assertArrayHasKey('id', $data); + $this->assertArrayHasKey('type', $data); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriod/ResourceTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriod/ResourceTest.php new file mode 100644 index 000000000..2fef00ec6 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriod/ResourceTest.php @@ -0,0 +1,40 @@ +setResource($person); + + $this->assertSame($person, $resource->getResource()); + $this->assertNull($resource->getThirdParty()); + + $resource->setResource($thirdParty); + + $this->assertSame($thirdParty, $resource->getResource()); + $this->assertNull($resource->getPerson()); + + // we repeat adding a person, to ensure that third party is + // well reset + $resource->setResource($person); + $this->assertSame($person, $resource->getResource()); + $this->assertNull($resource->getThirdParty()); + + $resource->setResource(null); + $this->assertNull($resource->getThirdParty()); + $this->assertNull($resource->getPerson()); + $this->assertNull($resource->getResource()); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php index aac6454a8..744cc76dc 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php @@ -25,6 +25,8 @@ namespace Chill\PersonBundle\Tests\Entity; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\Person; +use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase { @@ -120,4 +122,56 @@ class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase $this->assertNull($period->getOpenParticipationContainsPerson($person4)); $this->assertEquals(1, $period->getParticipationsContainsPerson($person4)->count()); } + + public function testRequestor() + { + $period = new AccompanyingPeriod(new \DateTime()); + $person = new Person(); + $thirdParty = new ThirdParty(); + + $this->assertNull($period->getRequestorThirdParty()); + $this->assertNull($period->getRequestorPerson()); + $this->assertNull($period->getRequestor()); + + $period->setRequestor($person); + $this->assertNull($period->getRequestorThirdParty()); + $this->assertSame($person, $period->getRequestorPerson()); + $this->assertSame($person, $period->getRequestor()); + + $period->setRequestor($thirdParty); + $this->assertNull($period->getRequestorPerson()); + $this->assertSame($thirdParty, $period->getRequestorThirdParty()); + $this->assertSame($thirdParty, $period->getRequestor()); + + $period->setRequestor(NULL); + $this->assertNull($period->getRequestorThirdParty()); + $this->assertNull($period->getRequestorPerson()); + $this->assertNull($period->getRequestor()); + } + + public function testInitialComment() + { + $period = new AccompanyingPeriod(new \DateTime()); + $comment = new Comment(); + $replacingComment = new Comment(); + + $period->setInitialComment(NULL); + $this->assertNull($period->getInitialComment()); + + $period->setInitialComment($comment); + $this->assertSame($period->getInitialComment(), $comment); + $this->assertSame($period, $comment->getAccompanyingPeriod()); + $this->assertEquals(0, count($period->getComments()), "The initial comment should not appears in the list of comments"); + + $period->setInitialComment($replacingComment); + $this->assertSame($period->getInitialComment(), $replacingComment); + $this->assertSame($period, $replacingComment->getAccompanyingPeriod()); + $this->assertEquals(0, count($period->getComments()), "The initial comment should not appears in the list of comments"); + $this->assertNull($comment->getAccompanyingPeriod()); + + $period->setInitialComment(NULL); + $this->assertNull($period->getInitialComment()); + $this->assertNull($replacingComment->getAccompanyingPeriod()); + $this->assertEquals(0, count($period->getComments()), "The initial comment should not appears in the list of comments"); + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AgeAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AgeAggregatorTest.php index 690f873e7..299cc62d5 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AgeAggregatorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AgeAggregatorTest.php @@ -38,9 +38,7 @@ class AgeAggregatorTest extends AbstractAggregatorTest { static::bootKernel(); - $container = static::$kernel->getContainer(); - - $this->aggregator = $container->get('chill.person.export.aggregator_age'); + $this->aggregator = static::$container->get('chill.person.export.aggregator_age'); } public function getAggregator() @@ -63,7 +61,7 @@ class AgeAggregatorTest extends AbstractAggregatorTest static::bootKernel(); } - $em = static::$kernel->getContainer() + $em = static::$container ->get('doctrine.orm.entity_manager'); return array( diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/GenderAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/GenderAggregatorTest.php index fd91305dd..724a793e3 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/GenderAggregatorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/GenderAggregatorTest.php @@ -37,9 +37,7 @@ class GenderAggregatorTest extends AbstractAggregatorTest { static::bootKernel(); - $container = static::$kernel->getContainer(); - - $this->aggregator = $container->get('chill.person.export.aggregator_gender'); + $this->aggregator = static::$container->get('chill.person.export.aggregator_gender'); } public function getAggregator() @@ -60,7 +58,7 @@ class GenderAggregatorTest extends AbstractAggregatorTest static::bootKernel(); } - $em = static::$kernel->getContainer() + $em = static::$container ->get('doctrine.orm.entity_manager'); return array( diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/NationalityAggregator.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/NationalityAggregatorTest.php similarity index 83% rename from src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/NationalityAggregator.php rename to src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/NationalityAggregatorTest.php index 522b8edcd..1c358530c 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/NationalityAggregator.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/NationalityAggregatorTest.php @@ -17,7 +17,6 @@ */ namespace Chill\PersonBundle\Tests\Export\Aggregator; -require_once '/home/julien/dev/chill-dev/vendor/chill-project/main/Test/Export/AbstractAggregatorTest.php'; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; /** @@ -25,7 +24,7 @@ use Chill\MainBundle\Test\Export\AbstractAggregatorTest; * * @author Julien Fastré */ -class NationalityAggregator extends AbstractAggregatorTest +class NationalityAggregatorTest extends AbstractAggregatorTest { /** * @@ -37,9 +36,7 @@ class NationalityAggregator extends AbstractAggregatorTest { static::bootKernel(); - $container = static::$kernel->getContainer(); - - $this->aggregator = $container->get('chill.person.export.aggregator_nationality'); + $this->aggregator = static::$container->get('chill.person.export.aggregator_nationality'); } public function getAggregator() @@ -61,7 +58,7 @@ class NationalityAggregator extends AbstractAggregatorTest static::bootKernel(); } - $em = static::$kernel->getContainer() + $em = static::$container ->get('doctrine.orm.entity_manager'); return array( diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountPersonTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountPersonTest.php index 5bb11f59a..8edc6e59d 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountPersonTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountPersonTest.php @@ -36,10 +36,7 @@ class CountPersonTest extends AbstractExportTest { static::bootKernel(); - /* @var $container \Symfony\Component\DependencyInjection\ContainerInterface */ - $container = self::$kernel->getContainer(); - - $this->export = $container->get('chill.person.export.export_count_person'); + $this->export = static::$container->get('chill.person.export.export_count_person'); } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListPersonTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListPersonTest.php index 44ab79f96..2ca369a24 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListPersonTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListPersonTest.php @@ -40,10 +40,7 @@ class ListPersonTest extends AbstractExportTest { static::bootKernel(); - /* @var $container \Symfony\Component\DependencyInjection\ContainerInterface */ - $container = self::$kernel->getContainer(); - - $this->export = $container->get('chill.person.export.list_person'); + $this->export = static::$container->get('chill.person.export.list_person'); // add a fake request with a default locale (used in translatable string) $prophet = new \Prophecy\Prophet; @@ -51,7 +48,7 @@ class ListPersonTest extends AbstractExportTest $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); - $container->get('request_stack') + static::$container->get('request_stack') ->push($request->reveal()); } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodFilterTest.php index a0f4ed1be..bf040619c 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodFilterTest.php @@ -36,10 +36,8 @@ class AccompanyingPeriodFilterTest extends AbstractFilterTest { static::bootKernel(); - $container = static::$kernel->getContainer(); - try { - $this->filter = $container->get('chill.person.export.filter_accompanying_period'); + $this->filter = static::$container->get('chill.person.export.filter_accompanying_period'); } catch (ServiceNotFoundException $e) { $this->markTestSkipped("The current configuration does not use accompanying_periods"); } @@ -67,7 +65,7 @@ class AccompanyingPeriodFilterTest extends AbstractFilterTest static::bootKernel(); } - $em = static::$kernel->getContainer() + $em = static::$container ->get('doctrine.orm.entity_manager'); return array( diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/BirthdayFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/BirthdayFilterTest.php index 1bcb040e4..e580c0f20 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/BirthdayFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/BirthdayFilterTest.php @@ -36,9 +36,7 @@ class BirthdayFilterTest extends AbstractFilterTest { static::bootKernel(); - $container = static::$kernel->getContainer(); - - $this->filter = $container->get('chill.person.export.filter_birthdate'); + $this->filter = static::$container->get('chill.person.export.filter_birthdate'); } @@ -63,7 +61,7 @@ class BirthdayFilterTest extends AbstractFilterTest static::bootKernel(); } - $em = static::$kernel->getContainer() + $em = static::$container ->get('doctrine.orm.entity_manager'); return array( diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/GenderFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/GenderFilterTest.php index dce198344..2d6d7e309 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/GenderFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/GenderFilterTest.php @@ -43,9 +43,7 @@ class GenderFilterTest extends AbstractFilterTest $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); - $container = static::$kernel->getContainer(); - - $this->filter = $container->get('chill.person.export.filter_gender'); + $this->filter = static::$container->get('chill.person.export.filter_gender'); } @@ -75,7 +73,7 @@ class GenderFilterTest extends AbstractFilterTest static::bootKernel(); } - $em = static::$kernel->getContainer() + $em = static::$container ->get('doctrine.orm.entity_manager'); return array( diff --git a/src/Bundle/ChillPersonBundle/Tests/Security/Authorization/PersonVoterTest.php b/src/Bundle/ChillPersonBundle/Tests/Security/Authorization/PersonVoterTest.php index 474fbc103..09f6cc2ba 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Security/Authorization/PersonVoterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Security/Authorization/PersonVoterTest.php @@ -60,7 +60,7 @@ class PersonVoterTest extends KernelTestCase public function setUp() { static::bootKernel(); - $this->voter = static::$kernel->getContainer() + $this->voter = static::$container ->get('chill.person.security.authorization.person'); $this->prophet = new \Prophecy\Prophet(); } diff --git a/src/Bundle/ChillPersonBundle/Tests/Workflows/AccompanyingPeriodLifecycle.php b/src/Bundle/ChillPersonBundle/Tests/Workflows/AccompanyingPeriodLifecycle.php new file mode 100644 index 000000000..df86d79a6 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Workflows/AccompanyingPeriodLifecycle.php @@ -0,0 +1,27 @@ +get(Registry::class); + $period = new AccompanyingPeriod(new \DateTime('now')); + $workflow = $registry->get($period); + + $this->assertArrayHasKey('DRAFT', $workflow->getMarking($period)->getPlaces()); + $this->assertTrue($workflow->can($period, 'confirm')); + } + +} diff --git a/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php b/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php index e885e0a26..2dcbb8230 100644 --- a/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php +++ b/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php @@ -45,7 +45,7 @@ class PersonListWidget implements WidgetInterface /** * Repository for persons * - * @var EntityRepository + * @var PersonRepository */ protected $personRepository; @@ -76,7 +76,7 @@ class PersonListWidget implements WidgetInterface protected $user; public function __construct( - EntityRepository $personRepostory, + PersonRepository $personRepostory, EntityManager $em, AuthorizationHelper $authorizationHelper, TokenStorage $tokenStorage diff --git a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml new file mode 100644 index 000000000..c3067be55 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml @@ -0,0 +1,766 @@ +components: + schemas: + # should go to main + Date: + type: object + properties: + datetime: + type: string + format: date-time + Scope: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - scope + name: + type: object + additionalProperties: + type: string + example: + fr: Social + ScopeById: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - scope + required: + - id + - scope + + # ok to stay here + Person: + type: object + properties: + id: + type: integer + readOnly: true + type: + type: string + enum: + - 'person' + firstName: + type: string + lastName: + type: string + text: + type: string + description: a canonical representation for the person name + readOnly: true + birthdate: + $ref: '#/components/schemas/Date' + phonenumber: + type: string + mobilenumber: + type: string + gender: + type: string + enum: + - man + - woman + - both + gender_numeric: + type: integer + description: a numerical representation of gender + readOnly: true + PersonById: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - 'person' + required: + - id + - type + # should go to third party + ThirdParty: + type: object + properties: + text: + type: string + ThirdPartyById: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - 'thirdparty' + required: + - id + - type + + # ok to stay here + AccompanyingPeriod: + type: object + properties: + type: + type: string + enum: + - accompanying_period + id: + type: integer + requestorAnonymous: + type: boolean + Resource: + type: object + properties: + type: + type: string + enum: + - 'accompanying_period_resource' + readOnly: true + id: + type: integer + readOnly: true + resource: + anyOf: + - $ref: '#/components/schemas/PersonById' + - $ref: '#/components/schemas/ThirdPartyById' + ResourceById: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - 'accompanying_period_resource' + required: + - id + - type + Comment: + type: object + properties: + type: + type: string + enum: + - 'accompanying_period_comment' + readOnly: true + id: + type: integer + readOnly: true + content: + type: string + CommentById: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - 'accompanying_period_comment' + required: + - id + - type + SocialIssue: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - 'social_issue' + parent_id: + type: integer + readOnly: true + children_ids: + type: array + items: + type: integer + readOnly: true + title: + type: object + additionalProperties: + type: string + example: + fr: Accompagnement Social Adulte + readOnly: true + text: + type: string + readOnly: true + +paths: + /1.0/person/person/{id}.json: + get: + tags: + - person + summary: Get a single person + parameters: + - name: id + in: path + required: true + description: The person's id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "OK" + content: + application/json: + schema: + $ref: "#/components/schemas/Person" + 403: + description: "Unauthorized" + /1.0/person/person.json: + post: + tags: + - person + summary: Create a single person + requestBody: + description: "A person" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Person' + responses: + 200: + description: "OK" + content: + application/json: + schema: + $ref: "#/components/schemas/Person" + 403: + description: "Unauthorized" + 422: + description: "Invalid data: the data is a valid json, could be deserialized, but does not pass validation" + + /1.0/person/social-work/social-issue.json: + get: + tags: + - social-issue + summary: Return a list of social work + responses: + 200: + description: "ok" + /1.0/person/social-work/social-issue/{id}.json: + get: + tags: + - social-issue + summary: Return a social issue by id + parameters: + - name: id + in: path + required: true + description: The social issue's id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/SocialIssue' + 404: + description: "not found" + 401: + description: "Unauthorized" + /1.0/person/accompanying-course/{id}.json: + get: + tags: + - person + summary: "Return the description for an accompanying course (accompanying period)" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + patch: + tags: + - person + summary: "Alter an accompanying course (accompanying period)" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "An accompanying period" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AccompanyingPeriod' + examples: + Set the requestor as anonymous: + value: + type: accompanying_period + id: 12345 + requestorAnonymous: true + Adding an initial comment: + value: + type: accompanying_period + id: 2668, + initialComment: + type: accompanying_period_comment + content: > + This is my an initial comment. + + Say hello to the new "parcours"! + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + /1.0/person/accompanying-course/{id}/requestor.json: + post: + tags: + - person + summary: "Add a requestor to the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A person or thirdparty" + required: true + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PersonById' + - $ref: '#/components/schemas/ThirdPartyById' + examples: + add person with id 50: + summary: "a person with id 50" + value: + type: person + id: 50 + add thirdparty with id 100: + summary: "a third party with id 100" + value: + type: thirdparty + id: 100 + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + delete: + tags: + - person + summary: "Remove the requestor for the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + + /1.0/person/accompanying-course/{id}/participation.json: + post: + tags: + - person + summary: "Add a participant to the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A person" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PersonById' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + delete: + tags: + - person + summary: "Remove the participant for the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A person" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PersonById' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + + /1.0/person/accompanying-course/{id}/resource.json: + post: + tags: + - person + summary: "Add a resource to the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A resource" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Resource' + examples: + add person with id 50: + summary: "a person with id 50" + value: + type: accompanying_period_resource + resource: + type: person + id: 50 + add thirdparty with id 100: + summary: "a third party with id 100" + value: + type: accompanying_period_resource + resource: + type: thirdparty + id: 100 + + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + delete: + tags: + - person + summary: "Remove the resource" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A resource" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ResourceById' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + + /1.0/person/accompanying-course/{id}/comment.json: + post: + tags: + - person + summary: "Add a comment to the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A comment" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + examples: + a single comment: + summary: "a simple comment" + value: + type: accompanying_period_comment + content: | + This is a funny comment I would like to share with you. + + Thank you for reading this ! + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + delete: + tags: + - person + summary: "Remove the comment" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A comment" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CommentById' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + + /1.0/person/accompanying-course/{id}/scope.json: + post: + tags: + - person + summary: "Add a scope to the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A comment" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Scope' + examples: + add a scope: + value: + type: scope + id: 5 + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + delete: + tags: + - person + summary: "Remove the scope" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A scope with his id" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScopeById' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + + /1.0/person/accompanying-course/{id}/socialissue.json: + post: + tags: + - person + summary: "Add a social issue to the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A social issue by id" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SocialIssue' + examples: + add a social issue: + value: + type: social_issue + id: 5 + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + delete: + tags: + - person + summary: "Remove the social issue" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A social issue with his id" + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SocialIssue' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + + /1.0/person/accompanying-course/{id}/confirm.json: + post: + tags: + - person + summary: confirm an accompanying course + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 400: + description: "transition cannot be applyed" diff --git a/src/Bundle/ChillPersonBundle/chill.webpack.config.js b/src/Bundle/ChillPersonBundle/chill.webpack.config.js index 7987ee124..53aed5d91 100644 --- a/src/Bundle/ChillPersonBundle/chill.webpack.config.js +++ b/src/Bundle/ChillPersonBundle/chill.webpack.config.js @@ -7,6 +7,6 @@ module.exports = function(encore, entries) encore.addAliases({ ChillPersonAssets: __dirname + '/Resources/public' }); - - encore.addEntry('accompanying_course', __dirname + '/Resources/public/js/AccompanyingCourse/index.js'); + + encore.addEntry('accompanying_course', __dirname + '/Resources/public/vuejs/AccompanyingCourse/index.js'); }; diff --git a/src/Bundle/ChillPersonBundle/config/services.yaml b/src/Bundle/ChillPersonBundle/config/services.yaml index f986a4ccb..523545900 100644 --- a/src/Bundle/ChillPersonBundle/config/services.yaml +++ b/src/Bundle/ChillPersonBundle/config/services.yaml @@ -2,7 +2,15 @@ parameters: # cl_chill_person.example.class: Chill\PersonBundle\Example services: + _defaults: + autowire: true + autoconfigure: true + Chill\PersonBundle\Serializer\Normalizer\: + resource: '../Serializer/Normalizer/' + autowire: true + tags: + - { name: 'serializer.normalizer', priority: 64 } chill.person.form.type.select2maritalstatus: class: Chill\PersonBundle\Form\Type\Select2MaritalStatusType @@ -27,10 +35,16 @@ services: public: true tags: - { name: chill.timeline, context: 'person' } - + chill.person.birthdate_validation: class: Chill\PersonBundle\Validator\Constraints\BirthdateValidator arguments: - "%chill_person.validation.birtdate_not_before%" tags: - { name: validator.constraint_validator, alias: birthdate_not_before } + + Chill\PersonBundle\Repository\: + autowire: true + autoconfigure: true + resource: '../Repository/' + tags: ['doctrine.repository_service'] diff --git a/src/Bundle/ChillPersonBundle/config/services/controller.yaml b/src/Bundle/ChillPersonBundle/config/services/controller.yaml index 41a5ac719..311950883 100644 --- a/src/Bundle/ChillPersonBundle/config/services/controller.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/controller.yaml @@ -51,4 +51,11 @@ services: arguments: $eventDispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface' $validator: '@Symfony\Component\Validator\Validator\ValidatorInterface' + $registry: '@Symfony\Component\Workflow\Registry' tags: ['controller.service_arguments'] + + Chill\PersonBundle\Controller\PersonApiController: + arguments: + $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' + tags: ['controller.service_arguments'] + diff --git a/src/Bundle/ChillPersonBundle/config/services/repository.yaml b/src/Bundle/ChillPersonBundle/config/services/repository.yaml index b99402bcf..72ca27e24 100644 --- a/src/Bundle/ChillPersonBundle/config/services/repository.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/repository.yaml @@ -1,30 +1,6 @@ services: - chill.person.repository.person: class: Chill\PersonBundle\Repository\PersonRepository - factory: ['@doctrine.orm.entity_manager', getRepository] - arguments: - - 'Chill\PersonBundle\Entity\Person' + autowire: true + autoconfigure: true Chill\PersonBundle\Repository\PersonRepository: '@chill.person.repository.person' - - Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepository: - class: Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepository - factory: ['@doctrine.orm.entity_manager', getRepository] - arguments: - - 'Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive' - - Chill\PersonBundle\Repository\AccompanyingPeriodRepository: - class: Chill\PersonBundle\Repository\AccompanyingPeriodRepository - tags: [ doctrine.repository_service ] - arguments: - - '@Doctrine\Persistence\ManagerRegistry' - - Chill\PersonBundle\Repository\AccompanyingPeriodParticipationRepository: - arguments: - - '@Doctrine\Persistence\ManagerRegistry' - tags: [ doctrine.repository_service ] - - Chill\PersonBundle\Repository\AccompanyingPeriod\OriginRepository: - arguments: - - '@Doctrine\Persistence\ManagerRegistry' - tags: [ doctrine.repository_service ] diff --git a/src/Bundle/ChillPersonBundle/config/services/serializer.yaml b/src/Bundle/ChillPersonBundle/config/services/serializer.yaml index 092dc2320..f64b3121b 100644 --- a/src/Bundle/ChillPersonBundle/config/services/serializer.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/serializer.yaml @@ -1,15 +1,10 @@ --- services: - Chill\PersonBundle\Serializer\Normalizer\PersonNormalizer: - arguments: - $repository: '@Chill\PersonBundle\Repository\PersonRepository' + # note: normalizers are loaded from ../services.yaml + + Chill\PersonBundle\Serializer\Normalizer\: + autowire: true + resource: '../../Serializer/Normalizer' tags: - { name: 'serializer.normalizer', priority: 64 } - Chill\PersonBundle\Serializer\Normalizer\AccompanyingPeriodNormalizer: - tags: - - { name: 'serializer.normalizer', priority: 64 } - - Chill\PersonBundle\Serializer\Normalizer\AccompanyingPeriodParticipationNormalizer: - tags: - - { name: 'serializer.normalizer', priority: 64 } diff --git a/src/Bundle/ChillPersonBundle/config/services/templating.yaml b/src/Bundle/ChillPersonBundle/config/services/templating.yaml index 3456c96de..37e904884 100644 --- a/src/Bundle/ChillPersonBundle/config/services/templating.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/templating.yaml @@ -1,12 +1,19 @@ services: + Chill\PersonBundle\Templating\Entity\: + resource: '../../Templating/Entity' + tags: + - 'chill.render_entity' + Chill\PersonBundle\Templating\Entity\PersonRender: arguments: $configAltNamesHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper' tags: - 'chill.render_entity' - + Chill\PersonBundle\Templating\Entity\ClosingMotiveRender: arguments: $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' - tags: - - 'chill.render_entity' + + Chill\PersonBundle\Templating\Entity\SocialIssueRender: + arguments: + $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210518075908.php b/src/Bundle/ChillPersonBundle/migrations/Version20210518075908.php new file mode 100644 index 000000000..e2806e761 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210518075908.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE chill_person_accompanying_period_social_issues (accompanyingperiod_id INT NOT NULL, socialissue_id INT NOT NULL, PRIMARY KEY(accompanyingperiod_id, socialissue_id))'); + $this->addSql('CREATE INDEX IDX_CAFE078F550B0C53 ON chill_person_accompanying_period_social_issues (accompanyingperiod_id)'); + $this->addSql('CREATE INDEX IDX_CAFE078FA549916C ON chill_person_accompanying_period_social_issues (socialissue_id)'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_social_issues ADD CONSTRAINT FK_CAFE078F550B0C53 FOREIGN KEY (accompanyingperiod_id) REFERENCES chill_person_accompanying_period (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_social_issues ADD CONSTRAINT FK_CAFE078FA549916C FOREIGN KEY (socialissue_id) REFERENCES chill_person_social_issue (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE chill_person_accompanying_period_social_issues'); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210518162439.php b/src/Bundle/ChillPersonBundle/migrations/Version20210518162439.php new file mode 100644 index 000000000..6907a64c6 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210518162439.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE chill_person_accompanying_period ADD initialComment_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT FK_E260A8683111D50B FOREIGN KEY (initialComment_id) REFERENCES chill_person_accompanying_period_comment (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_E260A8683111D50B ON chill_person_accompanying_period (initialComment_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_accompanying_period DROP initialComment_id'); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210519204938.php b/src/Bundle/ChillPersonBundle/migrations/Version20210519204938.php new file mode 100644 index 000000000..c71f7b203 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210519204938.php @@ -0,0 +1,35 @@ +addSql('ALTER TABLE chill_person_accompanying_period ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period ADD updatedBy_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT FK_E260A86865FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_E260A86865FF1AEC ON chill_person_accompanying_period (updatedBy_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_accompanying_period DROP createdAt'); + $this->addSql('ALTER TABLE chill_person_accompanying_period DROP updatedAt'); + $this->addSql('ALTER TABLE chill_person_accompanying_period DROP updatedBy_id'); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210525211214.php b/src/Bundle/ChillPersonBundle/migrations/Version20210525211214.php new file mode 100644 index 000000000..15df9386e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210525211214.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE chill_person_accompanying_period_comment DROP CONSTRAINT FK_CD960EF3D7FA8EF0'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_comment ADD CONSTRAINT FK_CD960EF3D7FA8EF0 FOREIGN KEY (accompanyingPeriod_id) REFERENCES chill_person_accompanying_period (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_accompanying_period_comment DROP CONSTRAINT fk_cd960ef3d7fa8ef0'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_comment ADD CONSTRAINT fk_cd960ef3d7fa8ef0 FOREIGN KEY (accompanyingperiod_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index ab3a5bfe6..30536eaa7 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -153,6 +153,7 @@ Update accompanying period: Mettre à jour une période d'accompagnement Any accompanying periods are open: Aucune période d'accompagnement ouverte An accompanying period is open: Une période d'accompagnement est ouverte Accompanying period list: Périodes d'accompagnement +New accompanying course: Nouveau parcours d'accompagnement Choose a motive: Motif de fermeture Re-open accompanying period: Ré-ouvrir Re-Open a period: Ré-ouvrir @@ -162,6 +163,14 @@ Pediod closing form is not valid: Le formulaire n'est pas valide Accompanying user: Accompagnant No accompanying user: Aucun accompagnant No data given: Pas d'information +Create an accompanying course: Créer un parcours +This accompanying course is still a draft: Ce parcours est à l'état brouillon +Associated peoples: Usagers concernés +Resources: Interlocuteurs privilégiés +Social actions: Actions d'accompagnement +Last events on accompanying course: Dernières actions de suivi +Edit & activate accompanying course: Modifier et valider + # pickAPersonType Pick a person: Choisir une personne diff --git a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php b/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php new file mode 100644 index 000000000..63f6e9c3b --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php @@ -0,0 +1,122 @@ +getThirdParties()->getObjects(); + + foreach ($thirdParties as $name => $thirdParty) { + if ('a' === $name[0]) { + // this is an address + continue; + } + + foreach ($this->getCenters() as $center) { + $thirdParty->addCenter($center); + } + + $manager->persist($thirdParty); + } + + $manager->flush(); + } + + private function getCenters(): \Iterator + { + $references = \array_map(function($a) { return $a['ref']; }, + LoadCenters::$centers); + $number = random_int(1, count($references)); + + if ($number === 1) { + yield $this->getReference($references[\array_rand($references)]); + } else { + foreach (array_rand($references, $number) as $index) { + yield $this->getReference($references[$index]); + } + } + } + + public function getDependencies() + { + return [ + LoadCenters::class, + LoadPostalCodes::class + ]; + } + + private function getThirdParties(): ObjectSet + { + $loader = new NativeLoader(); + $objectSet = $loader->loadData([ + Address::class => [ + 'address{1..75}' => [ + 'street' => '', + 'streetNumber' => '', + 'validFrom' => '', + 'postCode' => $this->getPostalCode() + ], + ], + ThirdParty::class => [ + 'thirdparty{1..75}' => [ + 'name' => '', + 'telephone' => '', + 'email' => '', + 'comment' => '', + 'address' => '@address' + ] + ] + ]); + + return $objectSet; + } + + private function getPostalCode(): PostalCode + { + $ref = LoadPostalCodes::$refs[\array_rand(LoadPostalCodes::$refs)]; + return $this->getReference($ref); + if (count($this->postalCodesIds) === 0) { + // fill the postal codes + $this->em->createQuery('SELECT p.id FROM '.PostalCode::class) + ->getScalarResult(); + } + + $id = $this->postalCodesIds[\array_rand($this->postalCodesIds)]; + + return $this->em->getRepository(PostalCode::class) + ->find($id); + } + + private function createAddress(): ObjectSet + { + $loader = new NativeLoader(); + $objectSet = $loader->loadData([ + Address::class => [ + 'address1' => [ + 'name' => '', + 'telephone' => '', + 'email' => '', + 'comment' => '' + ] + ] + ]); + + return $objectSet; + + } + +} diff --git a/src/Bundle/ChillThirdPartyBundle/DependencyInjection/ChillThirdPartyExtension.php b/src/Bundle/ChillThirdPartyBundle/DependencyInjection/ChillThirdPartyExtension.php index 28e3c19df..7ada8db8e 100644 --- a/src/Bundle/ChillThirdPartyBundle/DependencyInjection/ChillThirdPartyExtension.php +++ b/src/Bundle/ChillThirdPartyBundle/DependencyInjection/ChillThirdPartyExtension.php @@ -33,6 +33,8 @@ class ChillThirdPartyExtension extends Extension implements PrependExtensionInte $loader->load('services/search.yaml'); $loader->load('services/templating.yaml'); $loader->load('services/menu.yaml'); + $loader->load('services/fixtures.yaml'); + $loader->load('services/serializer.yaml'); } public function prepend(ContainerBuilder $container) diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 62d12694b..6b766c5c8 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -28,17 +28,20 @@ use Doctrine\Common\Collections\ArrayCollection; use Chill\MainBundle\Entity\Center; use Symfony\Component\Validator\Constraints as Assert; use Chill\MainBundle\Entity\Address; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; /** * ThirdParty is a party recorded in the database. - * - * A party may be attached to multiple centers. Being attach to a center allow - * all users with the right 'CHILL_3PARTY_3PARTY_SEE', 'CHILL_3PARTY_3 to see, select and edit parties for this + * + * A party may be attached to multiple centers. Being attach to a center allow + * all users with the right 'CHILL_3PARTY_3PARTY_SEE', 'CHILL_3PARTY_3 to see, select and edit parties for this * center. * * @ORM\Table(name="chill_3party.third_party") * @ORM\Entity(repositoryClass="Chill\ThirdPartyBundle\Repository\ThirdPartyRepository") - * + * @DiscriminatorMap(typeProperty="type", mapping={ + * "thirdparty"=ThirdParty::class + *}) */ class ThirdParty { @@ -63,7 +66,7 @@ class ThirdParty * @var string|null * * @ORM\Column(name="telephone", type="string", length=64, nullable=true) - * @Assert\Regex("/^([\+{1}])([0-9\s*]{4,20})$/", + * @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" * ) */ @@ -91,13 +94,13 @@ class ThirdParty * @Assert\Count(min=1) */ private $type; - + /** * @var boolean * @ORM\Column(name="active", type="boolean", options={"defaut": true}) */ private $active = true; - + /** * @var Collection instances of Center * @ORM\ManyToMany(targetEntity="\Chill\MainBundle\Entity\Center") @@ -105,14 +108,15 @@ class ThirdParty * @Assert\Count(min=1) */ private $centers; - + /** * @var Address|null * @ORM\ManyToOne(targetEntity="\Chill\MainBundle\Entity\Address", * cascade={"persist", "remove"}) + * @ORM\JoinColumn(nullable=true, onDelete="SET NULL") */ private $address; - + /** * ThirdParty constructor. */ @@ -246,7 +250,7 @@ class ThirdParty { return $this->type; } - + /** * @return bool */ @@ -254,7 +258,7 @@ class ThirdParty { return $this->active; } - + /** * @return Collection */ @@ -262,7 +266,7 @@ class ThirdParty { return $this->centers; } - + /** * @param bool $active * @return $this @@ -272,7 +276,7 @@ class ThirdParty $this->active = $active; return $this; } - + /** * @param Center $center */ @@ -282,7 +286,7 @@ class ThirdParty $this->centers->add($center); } } - + /** * @param Center $center */ @@ -292,7 +296,7 @@ class ThirdParty $this->centers->removeElement($center); } } - + /** * @param Collection $centers * @return $this @@ -302,16 +306,16 @@ class ThirdParty foreach ($centers as $center) { $this->addCenter($center); } - + foreach ($this->centers as $center) { if (FALSE === $centers->contains($center)) { $this->removeCenter($center); } } - + return $this; } - + /** * @return Address|null */ @@ -319,7 +323,7 @@ class ThirdParty { return $this->address; } - + /** * @param Address $address * @return $this @@ -327,10 +331,10 @@ class ThirdParty public function setAddress(Address $address) { $this->address = $address; - + return $this; } - + /** * @return string */ diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php index c961b3601..65845b744 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php +++ b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php @@ -4,13 +4,18 @@ namespace Chill\ThirdPartyBundle\Repository; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Query; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; +use Chill\ThirdPartyBundle\Entity\ThirdParty; -/** - * ThirdPartyRepository - * - */ -class ThirdPartyRepository extends \Doctrine\ORM\EntityRepository + +class ThirdPartyRepository extends ServiceEntityRepository { + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ThirdParty::class); + } + /** * count amongst parties associated to $centers, with $terms parameters * diff --git a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php new file mode 100644 index 000000000..203331cb7 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php @@ -0,0 +1,33 @@ +getName(); + $data['id'] = $thirdParty->getId(); + $data['address'] = $this->normalizer->normalize($thirdParty->getAddress(), $format, + [ 'address_rendering' => 'short' ]); + + return $data; + } + + public function supportsNormalization($data, string $format = null) + { + return $data instanceof ThirdParty; + } + +} diff --git a/src/Bundle/ChillThirdPartyBundle/config/services.yaml b/src/Bundle/ChillThirdPartyBundle/config/services.yaml index 0baad4766..438ed3ff0 100644 --- a/src/Bundle/ChillThirdPartyBundle/config/services.yaml +++ b/src/Bundle/ChillThirdPartyBundle/config/services.yaml @@ -1 +1,13 @@ +--- services: + Chill\ThirdPartyBundle\Serializer\Normalizer\: + autowire: true + resource: '../Serializer/Normalizer/' + tags: + - { name: 'serializer.normalizer', priority: 64 } + + Chill\ThirdPartyBundle\Repository\: + autowire: true + resource: '../Repository/' + tags: + - { name: 'doctrine.repository_service' } diff --git a/src/Bundle/ChillThirdPartyBundle/config/services/fixtures.yaml b/src/Bundle/ChillThirdPartyBundle/config/services/fixtures.yaml new file mode 100644 index 000000000..d57dadd4a --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/config/services/fixtures.yaml @@ -0,0 +1,5 @@ +--- +services: + Chill\ThirdPartyBundle\DataFixtures\ORM\LoadThirdParty: + tags: + - { 'name': doctrine.fixture.orm } diff --git a/src/Bundle/ChillThirdPartyBundle/config/services/serializer.yaml b/src/Bundle/ChillThirdPartyBundle/config/services/serializer.yaml new file mode 100644 index 000000000..0baad4766 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/config/services/serializer.yaml @@ -0,0 +1 @@ +services: diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20210525211216.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20210525211216.php new file mode 100644 index 000000000..ae270a1d1 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20210525211216.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT FK_D952467BF5B7AF75'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT FK_D952467BF5B7AF75 FOREIGN KEY (address_id) REFERENCES chill_main_address (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT fk_d952467bf5b7af75'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT fk_d952467bf5b7af75 FOREIGN KEY (address_id) REFERENCES chill_main_address (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } +}