mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-07-01 22:46:13 +00:00
Merge branch '139_demandeur' into _23_addresses_form_2
This commit is contained in:
commit
41254db72f
@ -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": {
|
||||
|
@ -450,4 +450,39 @@ Full configuration example
|
||||
single-collection: single
|
||||
base_role: null
|
||||
|
||||
Maintaining an OpenApi
|
||||
======================
|
||||
|
||||
.. note::
|
||||
|
||||
This is experimental and may change. Keep reading this part.
|
||||
|
||||
Accessing swagger
|
||||
-----------------
|
||||
|
||||
The swagger UI is accessible through `<http://localhost:8001/_dev/swagger>`_
|
||||
|
||||
This is possible only in development mode.
|
||||
|
||||
You must be authenticated with a valid user to access it.
|
||||
|
||||
Maintaining specs
|
||||
-----------------
|
||||
|
||||
Each bundle should have an `open api 3.0 spec file <https://swagger.io/docs/specification/about/>`_ at the bundle's root, and name :code:`chill.api.specs.yaml`.
|
||||
|
||||
.. warning::
|
||||
|
||||
Update the command :code:`specs-build` into `package.json` when adding a new api file.
|
||||
|
||||
The specs may be compiled from the different bundles filed, and validated using docker node:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./docker-node.sh
|
||||
# build the openapi
|
||||
yarn specs-build
|
||||
# validate
|
||||
yarn specs-validate
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -25,12 +25,19 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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'];
|
||||
}
|
||||
|
||||
|
@ -39,8 +39,17 @@ div.subheader {
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
//// VUEJS ////
|
||||
//// SCRATCH BUTTONS
|
||||
.sc-button {
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
&.bt-remove {
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// VUEJS ////
|
||||
div.vue-component {
|
||||
padding: 1.5em;
|
||||
margin: 2em 0;
|
||||
@ -95,33 +104,47 @@ div.vue-component {
|
||||
}
|
||||
|
||||
//// AddPersons modal
|
||||
div.modal-body.up {
|
||||
margin: auto 4em;
|
||||
div.search {
|
||||
position: relative;
|
||||
input {
|
||||
padding: 1.2em 1.5em 1.2em 2.5em;
|
||||
margin: 1em 0;
|
||||
div.body-head {
|
||||
overflow-y: unset;
|
||||
div.modal-body:first-child {
|
||||
margin: auto 4em;
|
||||
div.search {
|
||||
position: relative;
|
||||
input {
|
||||
padding: 1.2em 1.5em 1.2em 2.5em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
i {
|
||||
position: absolute;
|
||||
opacity: 0.5;
|
||||
padding: 0.65em 0;
|
||||
top: 50%;
|
||||
}
|
||||
i.fa-search {
|
||||
left: 0.5em;
|
||||
}
|
||||
i.fa-times {
|
||||
right: 1em;
|
||||
padding: 0.75em 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
i {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0.5em;
|
||||
padding: 0.65em 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
}
|
||||
div.modal-body:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
div.results {
|
||||
div.count {
|
||||
margin: -0.5em 0 0.7em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
div.count {
|
||||
margin: -0.5em 0 0.7em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
div.results {
|
||||
div.list-item {
|
||||
line-height: 26pt;
|
||||
padding: 0.3em 0.8em;
|
||||
padding: 0.4em 0.8em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
&.checked {
|
||||
@ -132,11 +155,20 @@ div.results {
|
||||
& > input {
|
||||
margin-right: 0.8em;
|
||||
}
|
||||
span:not(.name) {
|
||||
margin-left: 0.5em;
|
||||
opacity: 0.5;
|
||||
font-size: 90%;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
div.right_actions {
|
||||
margin: 0 0 0 auto;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
& > * {
|
||||
margin-left: 0.5em;
|
||||
align-self: baseline;
|
||||
}
|
||||
a.sc-button {
|
||||
border: 1px solid lightgrey;
|
||||
@ -146,7 +178,6 @@ div.results {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discret {
|
||||
color: grey;
|
||||
margin-right: 1em;
|
||||
|
@ -9,8 +9,8 @@
|
||||
<button class="close sc-button grey" @click="$emit('close')">
|
||||
<i class="fa fa-times" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="modal-body up" style="overflow-y: unset;">
|
||||
<slot name="body-fixed"></slot>
|
||||
<div class="body-head">
|
||||
<slot name="body-head"></slot>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body"></slot>
|
||||
|
36
src/Bundle/ChillMainBundle/Search/Model/Result.php
Normal file
36
src/Bundle/ChillMainBundle/Search/Model/Result.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Search\Model;
|
||||
|
||||
class Result
|
||||
{
|
||||
private float $relevance;
|
||||
|
||||
/**
|
||||
* mixed an arbitrary result
|
||||
*/
|
||||
private $result;
|
||||
|
||||
/**
|
||||
* @param float $relevance
|
||||
* @param $result
|
||||
*/
|
||||
public function __construct(float $relevance, $result)
|
||||
{
|
||||
$this->relevance = $relevance;
|
||||
$this->result = $result;
|
||||
}
|
||||
|
||||
public function getRelevance(): float
|
||||
{
|
||||
return $this->relevance;
|
||||
}
|
||||
|
||||
public function getResult()
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
89
src/Bundle/ChillMainBundle/Search/SearchApi.php
Normal file
89
src/Bundle/ChillMainBundle/Search/SearchApi.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Search;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Chill\MainBundle\Search\SearchProvider;
|
||||
use Symfony\Component\VarDumper\Resources\functions\dump;
|
||||
|
||||
/**
|
||||
* ***Warning*** This is an incomplete implementation ***Warning***
|
||||
*/
|
||||
class SearchApi
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
private SearchProvider $search;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, SearchProvider $search)
|
||||
{
|
||||
$this->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)
|
||||
;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
class AddressNormalizer implements NormalizerAwareInterface, NormalizerInterface
|
||||
{
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
public function normalize($address, string $format = null, array $context = [])
|
||||
{
|
||||
$data['address_id'] = $address->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;
|
||||
}
|
||||
|
||||
|
||||
}
|
59
src/Bundle/ChillMainBundle/chill.api.specs.yaml
Normal file
59
src/Bundle/ChillMainBundle/chill.api.specs.yaml
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
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:
|
||||
parameters:
|
||||
_format:
|
||||
name: _format
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- json
|
||||
|
||||
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"
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -1,3 +1,10 @@
|
||||
services:
|
||||
chill_main.search_provider:
|
||||
class: Chill\MainBundle\Search\SearchProvider
|
||||
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'
|
||||
|
@ -4,6 +4,10 @@ services:
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: 64 }
|
||||
|
||||
Chill\MainBundle\Serializer\Normalizer\AddressNormalizer:
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: 64 }
|
||||
|
||||
Chill\MainBundle\Serializer\Normalizer\DateNormalizer:
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: 64 }
|
||||
|
@ -12,6 +12,8 @@ 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;
|
||||
|
||||
class AccompanyingCourseApiController extends ApiController
|
||||
{
|
||||
@ -64,6 +66,50 @@ class AccompanyingCourseApiController extends ApiController
|
||||
return $this->json($participation);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
|
||||
return $this->json($accompanyingPeriod->getRequestor());
|
||||
}
|
||||
|
||||
protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response
|
||||
{
|
||||
$this->eventDispatcher->dispatch(
|
||||
|
@ -106,6 +106,11 @@ class AccompanyingCourseController extends Controller
|
||||
])
|
||||
);
|
||||
|
||||
//$accompanyingCourse->getRequestorPerson();
|
||||
//$accompanyingCourse->getRequestorThirdParty();
|
||||
//$accompanyingCourse->isRequestorAnonymous();
|
||||
//dump($accompanyingCourse); die;
|
||||
|
||||
switch ($_format) {
|
||||
case 'json':
|
||||
return $this->json($accompanyingCourse);
|
||||
|
@ -334,6 +334,18 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
||||
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
|
||||
]
|
||||
]
|
||||
|
||||
]
|
||||
|
@ -515,9 +515,9 @@ class AccompanyingPeriod
|
||||
return $this->requestorPerson;
|
||||
}
|
||||
|
||||
public function setRequestorPerson(Person $requestorPerson): self
|
||||
public function setRequestorPerson(Person $requestorPerson = null): self
|
||||
{
|
||||
$this->requestorPerson = ($this->requestorThirdParty === null) ? $requestorPerson : null;
|
||||
$this->requestorPerson = $requestorPerson;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -527,9 +527,9 @@ class AccompanyingPeriod
|
||||
return $this->requestorThirdParty;
|
||||
}
|
||||
|
||||
public function setRequestorThirdParty(ThirdParty $requestorThirdParty): self
|
||||
public function setRequestorThirdParty(ThirdParty $requestorThirdParty = null): self
|
||||
{
|
||||
$this->requestorThirdParty = ($this->requestorPerson === null) ? $requestorThirdParty : null;
|
||||
$this->requestorThirdParty = $requestorThirdParty;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -542,6 +542,37 @@ class AccompanyingPeriod
|
||||
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
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
@ -2,6 +2,8 @@
|
||||
<accompanying-course></accompanying-course>
|
||||
<persons-associated></persons-associated>
|
||||
<requestor></requestor>
|
||||
<interlocutors></interlocutors>
|
||||
<!--test></test-->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -10,13 +12,17 @@ import { mapState } from 'vuex'
|
||||
import AccompanyingCourse from './components/AccompanyingCourse.vue';
|
||||
import PersonsAssociated from './components/PersonsAssociated.vue';
|
||||
import Requestor from './components/Requestor.vue';
|
||||
import Interlocutors from './components/Interlocutors.vue';
|
||||
//import Test from './components/Test.vue';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
AccompanyingCourse,
|
||||
PersonsAssociated,
|
||||
Requestor
|
||||
Requestor,
|
||||
Interlocutors,
|
||||
//Test
|
||||
},
|
||||
computed: mapState([
|
||||
'accompanyingCourse'
|
||||
|
@ -1,18 +1,11 @@
|
||||
const
|
||||
locale = 'fr',
|
||||
format = 'json'
|
||||
, accompanying_period_id = window.accompanyingCourseId //tmp
|
||||
;
|
||||
|
||||
/*
|
||||
* Endpoint chill_person_accompanying_course_api_show
|
||||
* method GET, get AccompanyingCourse Object
|
||||
* Endpoint v.2 chill_api_single_accompanying_course__entity
|
||||
* method GET/HEAD, get AccompanyingCourse Instance
|
||||
*
|
||||
* @accompanying_period_id___ integer
|
||||
* @TODO var is not used but necessary in method signature
|
||||
* @id integer - id of accompanyingCourse
|
||||
*/
|
||||
let getAccompanyingCourse = (accompanying_period_id___) => { //tmp
|
||||
const url = `/${locale}/person/api/1.0/accompanying-course/${accompanying_period_id}/show.${format}`;
|
||||
const getAccompanyingCourse = (id) => {
|
||||
const url = `/api/1.0/person/accompanying-course/${id}.json`;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
@ -21,21 +14,50 @@ let getAccompanyingCourse = (accompanying_period_id___) => { //tmp
|
||||
};
|
||||
|
||||
/*
|
||||
* Endpoint chill_person_accompanying_course_api_add_participation,
|
||||
* Endpoint v.2 chill_api_single_accompanying_course_participation,
|
||||
* method POST/DELETE, add/close a participation to the accompanyingCourse
|
||||
*
|
||||
* @accompanying_period_id integer - id of accompanyingCourse
|
||||
* @person_id integer - id of person
|
||||
* @method string - POST or DELETE
|
||||
* @id integer - id of accompanyingCourse
|
||||
* @person_id integer - id of person
|
||||
* @method string - POST or DELETE
|
||||
*/
|
||||
let postParticipation = (accompanying_period_id, person_id, method) => {
|
||||
const url = `/${locale}/person/api/1.0/accompanying-course/${accompanying_period_id}/participation.${format}`
|
||||
const postParticipation = (id, person_id, method) => {
|
||||
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({id: person_id})
|
||||
body: JSON.stringify({person_id: person_id})
|
||||
})
|
||||
.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) => {
|
||||
const body = {};
|
||||
if (payload !== null) {
|
||||
const typeId = `${payload.result.type}_id`;
|
||||
body[typeId] = payload.result[typeId];
|
||||
};
|
||||
console.log(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(); }
|
||||
@ -45,5 +67,6 @@ let postParticipation = (accompanying_period_id, person_id, method) => {
|
||||
|
||||
export {
|
||||
getAccompanyingCourse,
|
||||
postParticipation
|
||||
postParticipation,
|
||||
postRequestor
|
||||
};
|
||||
|
@ -4,14 +4,46 @@
|
||||
<dl>
|
||||
<dt>{{ $t('course.id') }}</dt>
|
||||
<dd>{{ accompanyingCourse.id }}</dd>
|
||||
|
||||
<dt>{{ $t('course.user') }}</dt>
|
||||
<dd v-if="accompanyingCourse.user">{{ accompanyingCourse.user.username }}</dd>
|
||||
|
||||
<dt>{{ $t('course.status') }}</dt>
|
||||
<dd>
|
||||
<div v-if="accompanyingCourse.step !== 'DRAFT'"
|
||||
class="badge badge-pill badge-primary">
|
||||
{{ $t('course.step.active') }}</div>
|
||||
<div v-else class="badge badge-pill badge-secondary">
|
||||
{{ $t('course.step.draft') }}</div>
|
||||
</dd>
|
||||
|
||||
<dt>{{ $t('course.flags') }}</dt>
|
||||
<dd>
|
||||
<div v-if="accompanyingCourse.emergency === true"
|
||||
class="badge badge-pill badge-primary">
|
||||
{{ $t('course.emergency') }}</div>
|
||||
<div v-else class="badge badge-pill badge-secondary">
|
||||
{{ $t('course.emergency') }}</div>
|
||||
|
||||
<div v-if="accompanyingCourse.confidential === true"
|
||||
class="badge badge-pill badge-primary">
|
||||
{{ $t('course.confidential') }}</div>
|
||||
<div v-else class="badge badge-pill badge-secondary">
|
||||
{{ $t('course.confidential') }}</div>
|
||||
</dd>
|
||||
|
||||
<dt>{{ $t('course.opening_date') }}</dt>
|
||||
<dd>{{ $d(accompanyingCourse.openingDate.datetime, 'short') }}</dd>
|
||||
|
||||
<dt>{{ $t('course.closing_date') }}</dt>
|
||||
<dd>{{ $d(accompanyingCourse.closingDate.datetime, 'short') }}</dd>
|
||||
<dd v-if="accompanyingCourse.closingDate">{{ $d(accompanyingCourse.closingDate.datetime, 'short') }}</dd>
|
||||
|
||||
<dt>{{ $t('course.closing_motive') }}</dt>
|
||||
<dd v-if="accompanyingCourse.closingMotive">{{ accompanyingCourse.closingMotive.name.fr }}</dd>
|
||||
|
||||
<dt>{{ $t('course.remark') }}</dt>
|
||||
<dd>{{ accompanyingCourse.remark }}</dd>
|
||||
<dt>{{ $t('course.closing_motive') }}</dt>
|
||||
<dd>{{ accompanyingCourse.closing_motive }}</dd>
|
||||
|
||||
</dl>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<tr>
|
||||
|
||||
<td>{{ interlocutor.key }}</td>
|
||||
<td>{{ interlocutor.result.text }}</td>
|
||||
<td><span >
|
||||
</span>
|
||||
</td>
|
||||
<td><span >
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="sc-button bt-show" target="_blank"
|
||||
:href="url.show"
|
||||
:title="$t('action.show')">
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="sc-button bt-update" target="_blank"
|
||||
:href="url.edit"
|
||||
:title="$t('action.edit')">
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class="sc-button bt-remove"
|
||||
:title="$t('action.remove')"
|
||||
@click.prevent="$emit('remove', interlocutor)">
|
||||
</button>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'InterlocutorItem',
|
||||
props: ['interlocutor'],
|
||||
data() {
|
||||
return {
|
||||
url: {
|
||||
show: 'show', //'/fr/person/' + this.interlocutor.person.id + '/general',
|
||||
edit: 'edit' //'/fr/person/' + this.interlocutor.person.id + '/general/edit'
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['remove']
|
||||
}
|
||||
</script>
|
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
|
||||
<h3>{{ $t('interlocutors.title')}}</h3>
|
||||
|
||||
<label>{{ $tc('interlocutors.counter', counter) }}</label>
|
||||
<table class="rounded">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="chill-orange">{{ $t('interlocutors.firstname') }}</th>
|
||||
<th class="chill-orange">{{ $t('interlocutors.lastname') }}</th>
|
||||
<th>3</th>
|
||||
<th>4</th>
|
||||
<th class="chill-orange">{{ $t('action.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<interlocutor-item
|
||||
v-for="interlocutor in interlocutors"
|
||||
v-bind:interlocutor="interlocutor"
|
||||
v-bind:key="interlocutor.id"
|
||||
@remove="removeInterlocutor">
|
||||
</interlocutor-item>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<add-persons
|
||||
buttonTitle="interlocutors.add_interlocutors"
|
||||
modalTitle="interlocutors.add_interlocutors"
|
||||
v-bind:key="addPersons.key"
|
||||
v-bind:options="addPersons.options"
|
||||
@addNewPersons="addNewPersons"
|
||||
ref="addPersons"> <!-- to cast child method -->
|
||||
</add-persons>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'
|
||||
import InterlocutorItem from './InterlocutorItem.vue'
|
||||
|
||||
export default {
|
||||
name: 'Interlocutors',
|
||||
components: {
|
||||
AddPersons,
|
||||
InterlocutorItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addPersons: {
|
||||
key: 'interlocutors',
|
||||
options: {
|
||||
type: ['person', 'thirdparty'],
|
||||
priority: null,
|
||||
uniq: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
accompanyingCourse() {
|
||||
return this.$store.state.accompanyingCourse
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeInterlocutor() {
|
||||
console.log('@@ CLICK remove interlocutor: item');
|
||||
this.$store.dispatch('removeInterlocutor');
|
||||
},
|
||||
addNewPersons({ selected, modal }) {
|
||||
console.log('@@@ CLICK button addNewPersons', selected);
|
||||
selected.forEach(function(item) {
|
||||
this.$store.dispatch('addInterlocutor', item);
|
||||
}, this
|
||||
);
|
||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||
modal.showModal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -29,10 +29,12 @@
|
||||
</button>
|
||||
</li-->
|
||||
<li>
|
||||
<button class="sc-button bt-remove"
|
||||
<button v-if="!participation.endDate"
|
||||
class="sc-button bt-remove"
|
||||
:title="$t('action.remove')"
|
||||
@click.prevent="$emit('close', participation)">
|
||||
</button>
|
||||
<button v-else class="sc-button bt-remove disabled"></button>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="vue-component">
|
||||
<h3>{{ $t('persons_associated.title')}}</h3>
|
||||
<label>{{ $tc('persons_associated.counter', counter) }}</label>
|
||||
<table class="rounded">
|
||||
<table class="rounded">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="chill-orange">{{ $t('persons_associated.firstname') }}</th>
|
||||
@ -12,24 +12,26 @@
|
||||
<th class="chill-orange">{{ $t('action.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<person-item
|
||||
v-for="participation in participations"
|
||||
v-bind:participation="participation"
|
||||
v-bind:key="participation.id"
|
||||
<tbody>
|
||||
<person-item
|
||||
v-for="participation in participations"
|
||||
v-bind:participation="participation"
|
||||
v-bind:key="participation.id"
|
||||
@remove="removeParticipation"
|
||||
@close="closeParticipation">
|
||||
</person-item>
|
||||
</tbody>
|
||||
</table>
|
||||
<add-persons></add-persons>
|
||||
<ul class="record_actions">
|
||||
<!--li>
|
||||
<button class="sc-button orange" @click="savePersons">
|
||||
{{ $t('action.save') }}
|
||||
</button>
|
||||
</li-->
|
||||
</ul>
|
||||
|
||||
<add-persons
|
||||
buttonTitle="persons_associated.add_persons"
|
||||
modalTitle="add_persons.title"
|
||||
v-bind:key="addPersons.key"
|
||||
v-bind:options="addPersons.options"
|
||||
@addNewPersons="addNewPersons"
|
||||
ref="addPersons"> <!-- to cast child method -->
|
||||
</add-persons>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -40,27 +42,44 @@ import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'
|
||||
|
||||
export default {
|
||||
name: 'PersonsAssociated',
|
||||
components: {
|
||||
components: {
|
||||
PersonItem,
|
||||
AddPersons
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addPersons: {
|
||||
key: 'persons_associated',
|
||||
options: {
|
||||
type: ['person'],
|
||||
priority: null,
|
||||
uniq: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
participations: state => state.accompanyingCourse.participations,
|
||||
counter: state => state.accompanyingCourse.participations.length
|
||||
}),
|
||||
methods: {
|
||||
removeParticipation(item) {
|
||||
this.$store.dispatch('removeParticipation', item)
|
||||
console.log('@@ CLICK remove participation: item', item);
|
||||
this.$store.dispatch('removeParticipation', item);
|
||||
},
|
||||
closeParticipation(item) {
|
||||
console.log('@@ CLICK close participation: item', item);
|
||||
this.$store.dispatch('closeParticipation', item)
|
||||
this.$store.dispatch('closeParticipation', item);
|
||||
},
|
||||
/*
|
||||
savePersons() {
|
||||
console.log('[wip] saving persons');
|
||||
addNewPersons({ selected, modal }) {
|
||||
console.log('@@@ CLICK button addNewPersons', selected);
|
||||
selected.forEach(function(item) {
|
||||
this.$store.dispatch('addParticipation', item);
|
||||
}, this
|
||||
);
|
||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||
modal.showModal = false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,85 +1,116 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
|
||||
<h3>{{ $t('requestor.title') }}</h3>
|
||||
{{ accompanyingCourse.id }}
|
||||
{{ accompanyingCourse.remark }}<br><br>
|
||||
|
||||
<!-- TESTS AREA -->
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button class="sc-button bt-create" @click="modal1.showModal = true">
|
||||
{{ $t('action.show_modal') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="sc-button bt-create" @click="modal2.showModal = true">
|
||||
Ouvrir une seconde modale
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="accompanyingCourse.requestor">
|
||||
|
||||
<dt>{{ $t('requestor.type') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.type }}</dd>
|
||||
<dt>{{ $t('requestor.text') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.text }}</dd>
|
||||
<dt>{{ $t('requestor.anonymous') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestorAnonymous }}</dd>
|
||||
|
||||
<div v-if="accompanyingCourse.requestor.type === 'person'">
|
||||
<dt>{{ $t('requestor.person_id') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.person_id }}</dd>
|
||||
<dt>{{ $t('requestor.birthdate') }}</dt>
|
||||
<dd>{{ $d(accompanyingCourse.requestor.birthdate.datetime, 'short') }}</dd>
|
||||
<dt>{{ $t('requestor.center') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.center.name }}</dd>
|
||||
<dt>{{ $t('requestor.firstName') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.firstName }}</dd>
|
||||
<dt>{{ $t('requestor.lastName') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.lastName }}</dd>
|
||||
<dt>{{ $t('requestor.phonenumber') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.phonenumber }}</dd>
|
||||
<dt>{{ $t('requestor.mobilenumber') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.mobilenumber }}</dd>
|
||||
<dt>{{ $t('requestor.altNames') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.altNames }}</dd>
|
||||
</div>
|
||||
|
||||
<div v-if="accompanyingCourse.requestor.type === 'thirdparty'">
|
||||
<dt>{{ $t('requestor.person_id') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.thirdparty_id }}</dd>
|
||||
<dt>{{ $t('requestor.address') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.address.text }}</dd>
|
||||
<dt>{{ $t('requestor.location') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.address.postcode.name }}</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal1.showModal" :modalDialogClass="modal1.modalDialogClass" @close="modal1.showModal = false">
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">Le titre de ma modale</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus luctus facilisis suscipit. Cras pulvinar, purus sagittis pulvinar porta, enim ex posuere lacus, in pulvinar lectus magna in odio. Nullam iaculis congue lorem ac suscipit. Proin ut rutrum augue. Ut vehicula risus nec hendrerit ullamcorper. Ut volutpat eu mi eget viverra. Morbi dictum placerat suscipit. </p>
|
||||
<p>Quisque non erat tincidunt, lacinia justo ut, pulvinar nisl. Nunc id enim ut sem pretium interdum consectetur eu quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam posuere erat eget augue finibus luctus. Maecenas auctor, tortor non luctus ultrices, neque neque porttitor ex, nec lacinia lorem ligula et elit. Sed tempor nulla vitae lorem sollicitudin dictum. Vestibulum nec arcu eget elit pulvinar pretium. Phasellus facilisis metus sed diam luctus, feugiat scelerisque velit dignissim.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus luctus facilisis suscipit. Cras pulvinar, purus sagittis pulvinar porta, enim ex posuere lacus, in pulvinar lectus magna in odio. Nullam iaculis congue lorem ac suscipit. Proin ut rutrum augue. Ut vehicula risus nec hendrerit ullamcorper. Ut volutpat eu mi eget viverra. Morbi dictum placerat suscipit. </p>
|
||||
<p>Quisque non erat tincidunt, lacinia justo ut, pulvinar nisl. Nunc id enim ut sem pretium interdum consectetur eu quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam posuere erat eget augue finibus luctus. Maecenas auctor, tortor non luctus ultrices, neque neque porttitor ex, nec lacinia lorem ligula et elit. Sed tempor nulla vitae lorem sollicitudin dictum. Vestibulum nec arcu eget elit pulvinar pretium. Phasellus facilisis metus sed diam luctus, feugiat scelerisque velit dignissim.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus luctus facilisis suscipit. Cras pulvinar, purus sagittis pulvinar porta, enim ex posuere lacus, in pulvinar lectus magna in odio. Nullam iaculis congue lorem ac suscipit. Proin ut rutrum augue. Ut vehicula risus nec hendrerit ullamcorper. Ut volutpat eu mi eget viverra. Morbi dictum placerat suscipit. </p>
|
||||
<p>Quisque non erat tincidunt, lacinia justo ut, pulvinar nisl. Nunc id enim ut sem pretium interdum consectetur eu quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam posuere erat eget augue finibus luctus. Maecenas auctor, tortor non luctus ultrices, neque neque porttitor ex, nec lacinia lorem ligula et elit. Sed tempor nulla vitae lorem sollicitudin dictum. Vestibulum nec arcu eget elit pulvinar pretium. Phasellus facilisis metus sed diam luctus, feugiat scelerisque velit dignissim.</p>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button class="sc-button green" @click="modal1.showModal = false; modal2.showModal = true">
|
||||
{{ $t('action.next')}}</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isAnonymous" :value="value" />
|
||||
{{ $t('requestor.is_anonymous') }}
|
||||
</label>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal2.showModal" :modalDialogClass="modal2.modalDialogClass" @close="modal2.showModal = false">
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">Une autre modale</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<p>modal 2</p>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button class="sc-button green" @click="modal2.showModal = false">
|
||||
{{ $t('action.save')}}</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
<!-- END TESTS -->
|
||||
<button v-if="accompanyingCourse.requestor !== null"
|
||||
class="sc-button bt-remove"
|
||||
:title="$t('action.remove')"
|
||||
@click="removeRequestor">
|
||||
</button>
|
||||
|
||||
<add-persons v-if="accompanyingCourse.requestor === null"
|
||||
buttonTitle="requestor.add_requestor"
|
||||
modalTitle="requestor.add_requestor"
|
||||
v-bind:key="addPersons.key"
|
||||
v-bind:options="addPersons.options"
|
||||
@addNewPersons="addNewPersons"
|
||||
ref="addPersons"> <!-- to cast child method -->
|
||||
</add-persons>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal'
|
||||
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'
|
||||
|
||||
export default {
|
||||
name: 'Requestor',
|
||||
components: {
|
||||
Modal,
|
||||
AddPersons,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal1: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl" // modal-lg modal-md modal-sm
|
||||
},
|
||||
modal2: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-centered modal-sm" // modal-lg modal-md modal-sm
|
||||
addPersons: {
|
||||
key: 'requestor',
|
||||
options: {
|
||||
type: ['person', 'thirdparty'],
|
||||
priority: null,
|
||||
uniq: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
accompanyingCourse() {
|
||||
return this.$store.state.accompanyingCourse
|
||||
},
|
||||
isAnonymous: {
|
||||
set(value) {
|
||||
console.log('requestorIsAnonymous value',value);
|
||||
this.$store.dispatch('requestorIsAnonymous', value);
|
||||
},
|
||||
get() {
|
||||
return this.$store.state.accompanyingCourse.requestorAnonymous;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeRequestor() {
|
||||
console.log('@@ CLICK remove requestor: item');
|
||||
this.$store.dispatch('removeRequestor');
|
||||
},
|
||||
addNewPersons({ selected, modal }) {
|
||||
console.log('@@@ CLICK button addNewPersons', selected);
|
||||
selected.forEach(function(item) {
|
||||
this.$store.dispatch('addRequestor', item);
|
||||
}, this
|
||||
);
|
||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||
modal.showModal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h3>Tests</h3>
|
||||
|
||||
<!-- Modal -->
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button class="sc-button bt-create" @click="modal1.showModal = true">
|
||||
{{ $t('action.show_modal') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="sc-button bt-create" @click="modal2.showModal = true">
|
||||
Ouvrir une seconde modale
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal1.showModal" :modalDialogClass="modal1.modalDialogClass" @close="modal1.showModal = false">
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">Le titre de ma modale</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus luctus facilisis suscipit. Cras pulvinar, purus sagittis pulvinar porta, enim ex posuere lacus, in pulvinar lectus magna in odio. Nullam iaculis congue lorem ac suscipit. Proin ut rutrum augue. Ut vehicula risus nec hendrerit ullamcorper. Ut volutpat eu mi eget viverra. Morbi dictum placerat suscipit. </p>
|
||||
<p>Quisque non erat tincidunt, lacinia justo ut, pulvinar nisl. Nunc id enim ut sem pretium interdum consectetur eu quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam posuere erat eget augue finibus luctus. Maecenas auctor, tortor non luctus ultrices, neque neque porttitor ex, nec lacinia lorem ligula et elit. Sed tempor nulla vitae lorem sollicitudin dictum. Vestibulum nec arcu eget elit pulvinar pretium. Phasellus facilisis metus sed diam luctus, feugiat scelerisque velit dignissim.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus luctus facilisis suscipit. Cras pulvinar, purus sagittis pulvinar porta, enim ex posuere lacus, in pulvinar lectus magna in odio. Nullam iaculis congue lorem ac suscipit. Proin ut rutrum augue. Ut vehicula risus nec hendrerit ullamcorper. Ut volutpat eu mi eget viverra. Morbi dictum placerat suscipit. </p>
|
||||
<p>Quisque non erat tincidunt, lacinia justo ut, pulvinar nisl. Nunc id enim ut sem pretium interdum consectetur eu quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam posuere erat eget augue finibus luctus. Maecenas auctor, tortor non luctus ultrices, neque neque porttitor ex, nec lacinia lorem ligula et elit. Sed tempor nulla vitae lorem sollicitudin dictum. Vestibulum nec arcu eget elit pulvinar pretium. Phasellus facilisis metus sed diam luctus, feugiat scelerisque velit dignissim.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus luctus facilisis suscipit. Cras pulvinar, purus sagittis pulvinar porta, enim ex posuere lacus, in pulvinar lectus magna in odio. Nullam iaculis congue lorem ac suscipit. Proin ut rutrum augue. Ut vehicula risus nec hendrerit ullamcorper. Ut volutpat eu mi eget viverra. Morbi dictum placerat suscipit. </p>
|
||||
<p>Quisque non erat tincidunt, lacinia justo ut, pulvinar nisl. Nunc id enim ut sem pretium interdum consectetur eu quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam posuere erat eget augue finibus luctus. Maecenas auctor, tortor non luctus ultrices, neque neque porttitor ex, nec lacinia lorem ligula et elit. Sed tempor nulla vitae lorem sollicitudin dictum. Vestibulum nec arcu eget elit pulvinar pretium. Phasellus facilisis metus sed diam luctus, feugiat scelerisque velit dignissim.</p>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button class="sc-button green" @click="modal1.showModal = false; modal2.showModal = true">
|
||||
{{ $t('action.next')}}</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal2.showModal" :modalDialogClass="modal2.modalDialogClass" @close="modal2.showModal = false">
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">Une autre modale</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<p>modal 2</p>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button class="sc-button green" @click="modal2.showModal = false">
|
||||
{{ $t('action.save')}}</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
<!-- END Modal -->
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal'
|
||||
|
||||
export default {
|
||||
name: 'Test',
|
||||
components: {
|
||||
Modal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal1: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl" // modal-lg modal-md modal-sm
|
||||
},
|
||||
modal2: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-centered modal-sm" // modal-lg modal-md modal-sm
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
}
|
||||
}
|
||||
</script>
|
@ -9,19 +9,50 @@ const appMessages = {
|
||||
closing_date: "Date de clôture",
|
||||
remark: "Commentaire",
|
||||
closing_motive: "Motif de clôture",
|
||||
user: "TMS",
|
||||
flags: "Indicateurs",
|
||||
status: "État",
|
||||
step: {
|
||||
draft: "Brouillon",
|
||||
open: "Ouvert",
|
||||
active: "En file active"
|
||||
},
|
||||
emergency: "urgent",
|
||||
confidential: "confidentiel",
|
||||
},
|
||||
persons_associated: {
|
||||
title: "Usagers concernés",
|
||||
counter: "Pas d'usager | 1 usager | {count} usagers",
|
||||
firstname: "Prénom",
|
||||
lastname: "Nom",
|
||||
startdate: "Date d'entrée",
|
||||
firstname: "Prénom",
|
||||
lastname: "Nom",
|
||||
startdate: "Date d'entrée",
|
||||
enddate: "Date de sortie",
|
||||
addPerson: "Ajouter un usager",
|
||||
add_persons: "Ajouter des usagers",
|
||||
},
|
||||
requestor: {
|
||||
title: "Demandeur",
|
||||
add_requestor: "Ajouter un demandeur",
|
||||
is_anonymous: "Le demandeur est anonyme",
|
||||
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é",
|
||||
},
|
||||
interlocutors: {
|
||||
title: "Interlocuteurs privilégiés",
|
||||
counter: "Pas d'interlocuteur | 1 interlocuteur | {count} interlocuteurs",
|
||||
firstname: "Prénom",
|
||||
lastname: "Nom",
|
||||
add_interlocutors: "Ajouter des interlocuteurs",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import 'es6-promise/auto';
|
||||
import { createStore } from 'vuex';
|
||||
import addPersons from './modules/addPersons'
|
||||
import { getAccompanyingCourse, postParticipation } from '../api';
|
||||
import { getAccompanyingCourse, postParticipation, postRequestor } from '../api';
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production';
|
||||
|
||||
const id = window.accompanyingCourseId; //tmp
|
||||
const id = window.accompanyingCourseId;
|
||||
|
||||
let initPromise = getAccompanyingCourse(id)
|
||||
.then(accompanying_course => new Promise((resolve, reject) => {
|
||||
@ -13,7 +11,6 @@ let initPromise = getAccompanyingCourse(id)
|
||||
const store = createStore({
|
||||
strict: debug,
|
||||
modules: {
|
||||
addPersons
|
||||
},
|
||||
state: {
|
||||
accompanyingCourse: accompanying_course,
|
||||
@ -23,30 +20,36 @@ let initPromise = getAccompanyingCourse(id)
|
||||
},
|
||||
mutations: {
|
||||
removeParticipation(state, item) {
|
||||
//console.log('mutation: remove item', item.id);
|
||||
//console.log('### mutation: remove item', item.id);
|
||||
state.accompanyingCourse.participations = state.accompanyingCourse.participations.filter(participation => participation !== item);
|
||||
},
|
||||
closeParticipation(state, { participation, payload }) {
|
||||
console.log('### mutation: close item', { participation, payload });
|
||||
// trouve dans le state le payload et le supprime du state
|
||||
state.accompanyingCourse.participations = state.accompanyingCourse.participations.filter(participation => participation !== payload);
|
||||
// pousse la participation
|
||||
state.accompanyingCourse.participations.push(participation);
|
||||
//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) {
|
||||
state.accompanyingCourse.requestor = null;
|
||||
},
|
||||
addRequestor(state, requestor) {
|
||||
console.log(requestor);
|
||||
state.accompanyingCourse.requestor = requestor;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
removeParticipation({ commit }, payload) {
|
||||
commit('removeParticipation', payload);
|
||||
},
|
||||
closeParticipation({ commit }, payload) {
|
||||
//console.log('## action: fetch delete participation: payload', payload.person.id);
|
||||
postParticipation(id, payload.person.id, 'DELETE')
|
||||
console.log('## action: fetch delete participation: payload', payload);
|
||||
postParticipation(id, payload.person.person_id, 'DELETE')
|
||||
.then(participation => new Promise((resolve, reject) => {
|
||||
//console.log('payload', payload);
|
||||
commit('closeParticipation', { participation, payload });
|
||||
resolve();
|
||||
}))
|
||||
@ -54,22 +57,43 @@ let initPromise = getAccompanyingCourse(id)
|
||||
state.errorMsg.push(error.message);
|
||||
});
|
||||
},
|
||||
addParticipation(addPersons, payload) {
|
||||
//console.log('## action: fetch post participation: payload', payload.id);
|
||||
postParticipation(id, payload.id, 'POST')
|
||||
addParticipation({ commit }, payload) {
|
||||
console.log('## action: fetch post participation (select item): payload', payload);
|
||||
postParticipation(id, payload.result.person_id, 'POST')
|
||||
.then(participation => new Promise((resolve, reject) => {
|
||||
//console.log(participation, payload);
|
||||
addPersons.commit('addParticipation', participation);
|
||||
addPersons.commit('resetState', payload);
|
||||
commit('addParticipation', participation);
|
||||
resolve();
|
||||
}))
|
||||
.catch((error) => {
|
||||
state.errorMsg.push(error.message);
|
||||
});
|
||||
},
|
||||
removeRequestor({ commit }) {
|
||||
postRequestor(id, null, 'DELETE')
|
||||
.then(requestor => new Promise((resolve, reject) => {
|
||||
commit('removeRequestor');
|
||||
resolve();
|
||||
}))
|
||||
.catch((error) => {
|
||||
state.errorMsg.push(error.message);
|
||||
});
|
||||
},
|
||||
addRequestor({ commit }, payload) {
|
||||
console.log(payload);
|
||||
postRequestor(id, payload, 'POST')
|
||||
.then(requestor => new Promise((resolve, reject) => {
|
||||
commit('addRequestor', requestor);
|
||||
resolve();
|
||||
}))
|
||||
.catch((error) => {
|
||||
state.errorMsg.push(error.message);
|
||||
});
|
||||
},
|
||||
requestorIsAnonymous({ commit }, payload) {
|
||||
console.log('payload', payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
//console.log('store object', store.state.accompanyingCourse.id);
|
||||
resolve(store);
|
||||
}));
|
||||
|
||||
|
@ -1,76 +0,0 @@
|
||||
import { searchPersons } from 'ChillPersonAssets/vuejs/_api/AddPersons'
|
||||
import { postParticipation } from '../../api';
|
||||
|
||||
|
||||
// initial state
|
||||
const state = {
|
||||
query: "",
|
||||
suggested: [],
|
||||
selected: []
|
||||
}
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
selectedAndSuggested: state => {
|
||||
const uniqBy = (a, key) => [
|
||||
...new Map(
|
||||
a.map(x => [key(x), x])
|
||||
).values()
|
||||
];
|
||||
let union = [...new Set([
|
||||
...state.suggested.slice().reverse(),
|
||||
...state.selected.slice().reverse(),
|
||||
])];
|
||||
return uniqBy(union, k => k.id);
|
||||
}
|
||||
}
|
||||
|
||||
// mutations
|
||||
const mutations = {
|
||||
setQuery(state, query) {
|
||||
//console.log('q=', query);
|
||||
state.query = query;
|
||||
},
|
||||
loadSuggestions(state, suggested) {
|
||||
state.suggested = suggested;
|
||||
},
|
||||
updateSelected(state, value) {
|
||||
state.selected = value;
|
||||
},
|
||||
resetState(state, selected) {
|
||||
//console.log('avant', state.selected);
|
||||
state.selected = state.selected.filter(value => value !== selected);
|
||||
//console.log('après', state.selected);
|
||||
state.query = "";
|
||||
state.suggested = [];
|
||||
}
|
||||
}
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
setQuery({ commit }, payload) {
|
||||
//console.log('## action: setquery: payload', payload);
|
||||
commit('setQuery', payload.query);
|
||||
if (payload.query.length >= 3) {
|
||||
searchPersons(payload.query)
|
||||
.then(suggested => new Promise((resolve, reject) => {
|
||||
commit('loadSuggestions', suggested.results);
|
||||
resolve();
|
||||
}));
|
||||
} else {
|
||||
commit('loadSuggestions', []);
|
||||
}
|
||||
},
|
||||
updateSelected({ commit }, payload) {
|
||||
//console.log('## action: update selected values: payload', payload);
|
||||
commit('updateSelected', payload);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
//namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
@ -1,15 +1,23 @@
|
||||
const
|
||||
locale = 'fr',
|
||||
format = 'json'
|
||||
;
|
||||
/*
|
||||
* 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
|
||||
* Endpoint chill_person_search
|
||||
* method GET, get a list of persons
|
||||
*
|
||||
* @query string - the query to search for
|
||||
*/
|
||||
let searchPersons = (query) => {
|
||||
let url = `/${locale}/search.${format}?name=person_regular&q=${query}`;
|
||||
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(); }
|
||||
@ -17,4 +25,22 @@ let searchPersons = (query) => {
|
||||
});
|
||||
};
|
||||
|
||||
export { searchPersons };
|
||||
/*
|
||||
* 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 };
|
||||
|
@ -1,73 +1,87 @@
|
||||
<template>
|
||||
<button class="sc-button bt-create centered mt-4" @click="openModal">
|
||||
{{ $t('add_persons.search_add_others_persons') }}
|
||||
{{ $t(buttonTitle) }}
|
||||
</button>
|
||||
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<modal v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">{{ $t('add_persons.title') }}</h3>
|
||||
<h3 class="modal-title">{{ $t(modalTitle) }}</h3>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-fixed>
|
||||
<div class="search">
|
||||
<label style="float: right;">
|
||||
{{ $tc('add_persons.suggested_counter', suggestedCounter) }}
|
||||
</label>
|
||||
|
||||
<input id="search-persons"
|
||||
name="query"
|
||||
v-model="query"
|
||||
:placeholder="$t('add_persons.search_some_persons')"
|
||||
ref="search" />
|
||||
<i class="fa fa-search fa-lg"></i>
|
||||
<template v-slot:body-head>
|
||||
<div class="modal-body">
|
||||
<div class="search">
|
||||
|
||||
<label style="float: right;">
|
||||
{{ $tc('add_persons.suggested_counter', suggestedCounter) }}
|
||||
</label>
|
||||
|
||||
<input id="search-persons"
|
||||
name="query"
|
||||
v-model="query"
|
||||
:placeholder="$t('add_persons.search_some_persons')"
|
||||
ref="search" />
|
||||
<i class="fa fa-search fa-lg"></i>
|
||||
<i class="fa fa-times" v-if="queryLength >= 3" @click="resetSuggestion"></i>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:body>
|
||||
<!--span class="discret">Selection: {{ selected }}</span-->
|
||||
<div class="results">
|
||||
<div class="modal-body">
|
||||
<div class="count">
|
||||
<span>
|
||||
<a v-if="suggestedCounter > 0" href="#">
|
||||
{{ $t('action.check_all')}}</a>
|
||||
<a v-if="selectedCounter > 0" href="#">
|
||||
{{ $t('action.reset')}}</a>
|
||||
<a v-if="suggestedCounter > 2" @click="selectAll">
|
||||
{{ $t('action.check_all')}}
|
||||
</a>
|
||||
<a v-if="selectedCounter > 0" @click="resetSelection">
|
||||
<i v-if="suggestedCounter > 2"> • </i>
|
||||
{{ $t('action.reset')}}
|
||||
</a>
|
||||
</span>
|
||||
<span v-if="selectedCounter > 0">
|
||||
{{ $tc('add_persons.selected_counter', selectedCounter) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<person-suggestion
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:body>
|
||||
<!--span class="discret">Selection: {{ selected }}</span-->
|
||||
<div class="results">
|
||||
|
||||
<person-suggestion
|
||||
v-for="item in this.selectedAndSuggested.slice().reverse()"
|
||||
v-bind:item="item"
|
||||
v-bind:key="item.id">
|
||||
v-bind:key="itemKey(item)"
|
||||
v-bind:item="item"
|
||||
v-bind:search="search"
|
||||
@updateSelected="updateSelected">
|
||||
</person-suggestion>
|
||||
|
||||
|
||||
<button v-if="query.length >= 3" class="sc-button bt-create ml-5 mt-2" name="createPerson">
|
||||
{{ $t('action.create') }} "{{ query }}"
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-slot:footer>
|
||||
<button class="sc-button green" @click="addNewPersons">
|
||||
<button class="sc-button green"
|
||||
@click.prevent="$emit('addNewPersons', { selected, modal })">
|
||||
<i class="fa fa-plus fa-fw"></i>{{ $t('action.add')}}
|
||||
</button>
|
||||
{{ $t(checkUniq) }}
|
||||
</template>
|
||||
|
||||
|
||||
</modal>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||
import PersonSuggestion from 'ChillPersonAssets/vuejs/_components/PersonSuggestion';
|
||||
import { searchPersons, searchPersons_2 } from 'ChillPersonAssets/vuejs/_api/AddPersons';
|
||||
|
||||
export default {
|
||||
name: 'AddPersons',
|
||||
@ -75,40 +89,69 @@ export default {
|
||||
Modal,
|
||||
PersonSuggestion,
|
||||
},
|
||||
props: [
|
||||
'buttonTitle',
|
||||
'modalTitle',
|
||||
'options'
|
||||
],
|
||||
emits: ['addNewPersons'],
|
||||
data() {
|
||||
return {
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl"
|
||||
},
|
||||
search: {
|
||||
query: "",
|
||||
suggested: [],
|
||||
selected: []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
addPersons: state => state.addPersons
|
||||
}),
|
||||
query: {
|
||||
set(query) {
|
||||
this.$store.dispatch('setQuery', { query });
|
||||
return this.setQuery(query);
|
||||
},
|
||||
get() {
|
||||
return this.addPersons.query;
|
||||
return this.search.query;
|
||||
}
|
||||
},
|
||||
queryLength() {
|
||||
return this.search.query.length;
|
||||
},
|
||||
suggested() {
|
||||
return this.addPersons.suggested;
|
||||
return this.search.suggested;
|
||||
},
|
||||
suggestedCounter() {
|
||||
return this.addPersons.suggested.length;
|
||||
},
|
||||
return this.search.suggested.length;
|
||||
},
|
||||
selected() {
|
||||
return this.addPersons.selected;
|
||||
},
|
||||
return this.search.selected;
|
||||
},
|
||||
selectedCounter() {
|
||||
return this.addPersons.selected.length;
|
||||
return this.search.selected.length;
|
||||
},
|
||||
selectedAndSuggested() {
|
||||
return this.$store.getters.selectedAndSuggested;
|
||||
const uniqBy = (a, key) => [
|
||||
...new Map(
|
||||
a.map(x => [key(x), x])
|
||||
).values()
|
||||
];
|
||||
let union = [...new Set([
|
||||
...this.suggested.slice().reverse(),
|
||||
...this.selected.slice().reverse(),
|
||||
])];
|
||||
return uniqBy(union, k => k.key);
|
||||
},
|
||||
options() {
|
||||
return this.options;
|
||||
},
|
||||
checkUniq() {
|
||||
if (this.options.uniq === true && this.selectedCounter > 1) {
|
||||
return "error_only_one_person";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -118,15 +161,47 @@ export default {
|
||||
this.$refs.search.focus();
|
||||
})
|
||||
},
|
||||
addNewPersons() {
|
||||
console.log('@@@ CLICK button addPersons')
|
||||
this.selected.forEach(function(item) {
|
||||
//console.log('# dispatch action for each item', item);
|
||||
this.$store.dispatch('addParticipation', item);
|
||||
}, this
|
||||
);
|
||||
this.modal.showModal = false;
|
||||
setQuery(query) {
|
||||
this.search.query = query;
|
||||
if (query.length >= 3) {
|
||||
searchPersons_2({ query, options: this.options })
|
||||
.then(suggested => new Promise((resolve, reject) => {
|
||||
this.loadSuggestions(suggested.results);
|
||||
resolve();
|
||||
}));
|
||||
} else {
|
||||
this.loadSuggestions([]);
|
||||
}
|
||||
},
|
||||
loadSuggestions(suggested) {
|
||||
this.search.suggested = suggested;
|
||||
this.search.suggested.forEach(function(item) {
|
||||
item.key = this.itemKey(item);
|
||||
}, this);
|
||||
},
|
||||
updateSelected(value) {
|
||||
this.search.selected = value;
|
||||
},
|
||||
resetSearch() {
|
||||
this.resetSelection();
|
||||
this.resetSuggestion();
|
||||
},
|
||||
resetSuggestion() {
|
||||
this.search.query = "";
|
||||
this.search.suggested = [];
|
||||
},
|
||||
resetSelection() {
|
||||
this.search.selected = [];
|
||||
},
|
||||
selectAll() {
|
||||
this.search.suggested.forEach(function(item) {
|
||||
this.search.selected.push(item);
|
||||
}, this);
|
||||
},
|
||||
itemKey(item) {
|
||||
let type = item.result.type;
|
||||
return type + item.result[type + '_id'];
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,53 +1,52 @@
|
||||
<template>
|
||||
<div class="list-item" :class="{ checked: isChecked }">
|
||||
<div class="container">
|
||||
|
||||
<!--a class="discret" target="_blank" :href="url.show">{{ item.id }}</a-->
|
||||
<input class=""
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
:value="item" />
|
||||
<div class="container">
|
||||
<input type="checkbox"
|
||||
v-model="selected"
|
||||
:value="item" />
|
||||
</div>
|
||||
|
||||
{{ item.text }}
|
||||
<suggestion-person
|
||||
v-if="item.result.type === 'person'"
|
||||
v-bind:item="item">
|
||||
</suggestion-person>
|
||||
|
||||
</div>
|
||||
<div class="right_actions">
|
||||
|
||||
<span class="badge badge-pill badge-secondary" :title="item.id">
|
||||
{{ $t('item.type_person') }}
|
||||
</span>
|
||||
<a class="sc-button bt-show" target="_blank" :title="item.id" :href="url.show"></a>
|
||||
|
||||
</div>
|
||||
<suggestion-third-party
|
||||
v-if="item.result.type === 'thirdparty'"
|
||||
v-bind:item="item">
|
||||
</suggestion-third-party>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import SuggestionPerson from './PersonSuggestion/Person';
|
||||
import SuggestionThirdParty from './PersonSuggestion/ThirdParty';
|
||||
|
||||
export default {
|
||||
name: 'PersonSuggestion',
|
||||
props: ['item'],
|
||||
data() {
|
||||
return {
|
||||
url: {
|
||||
show: '/fr/person/' + this.item.id + '/general',
|
||||
edit: '/fr/person/' + this.item.id + '/general/edit'
|
||||
}
|
||||
}
|
||||
components: {
|
||||
SuggestionPerson,
|
||||
SuggestionThirdParty,
|
||||
},
|
||||
props: [
|
||||
'item',
|
||||
'search'
|
||||
],
|
||||
emits: ['updateSelected'],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$store.dispatch('updateSelected', value);
|
||||
this.$emit('updateSelected', value);
|
||||
},
|
||||
get() {
|
||||
return this.$store.state.addPersons.selected;
|
||||
return this.search.selected;
|
||||
}
|
||||
},
|
||||
isChecked() {
|
||||
return (this.selected.indexOf(this.item) === -1) ? false : true;
|
||||
return (this.search.selected.indexOf(this.item) === -1) ? false : true;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
|
||||
<span class="name">
|
||||
{{ item.result.text }}
|
||||
</span>
|
||||
<span class="birthday">
|
||||
{{ $d(item.result.birthdate.datetime, 'short') }}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div class="right_actions">
|
||||
|
||||
<span class="badge badge-pill badge-secondary" :title="item.key">
|
||||
{{ $t('item.type_person') }}
|
||||
</span>
|
||||
<a class="sc-button bt-show" target="_blank" :title="item.key" :href="url.show"></a>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SuggestionPerson',
|
||||
props: ['item'],
|
||||
data() {
|
||||
return {
|
||||
url: {
|
||||
show: '/fr/person/' + this.item.result.person_id + '/general',
|
||||
edit: '/fr/person/' + this.item.result.person_id + '/general/edit'
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
|
||||
<span class="name">
|
||||
{{ item.result.text }}
|
||||
</span>
|
||||
<span class="location">
|
||||
{{ item.result.address.text }} -
|
||||
{{ item.result.address.postcode.name }}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div class="right_actions">
|
||||
|
||||
<span class="badge badge-pill badge-secondary" :title="item.key">
|
||||
{{ $t('item.type_thirdparty') }}
|
||||
</span>
|
||||
<a class="sc-button bt-show" target="_blank" :title="item.key" :href="url.show"></a>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SuggestionThirdParty',
|
||||
props: ['item'],
|
||||
data() {
|
||||
return {
|
||||
url: {
|
||||
show: '/fr/thirdparty/thirdparty/' + this.item.result.thirdparty_id + '/show',
|
||||
edit: '/fr/thirdparty/thirdparty/' + this.item.result.thirdparty_id + '/edit'
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
@ -1,7 +1,6 @@
|
||||
const personMessages = {
|
||||
fr: {
|
||||
add_persons: {
|
||||
search_add_others_persons: "Rechercher et ajouter d'autres usagers",
|
||||
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",
|
||||
@ -9,10 +8,11 @@ const personMessages = {
|
||||
},
|
||||
item: {
|
||||
type_person: "Usager",
|
||||
type_tms: "TMS",
|
||||
type_3rdparty: "Tiers",
|
||||
type_menage: "Ménage"
|
||||
}
|
||||
type_user: "TMS",
|
||||
type_thirdparty: "Tiers",
|
||||
type_household: "Ménage"
|
||||
},
|
||||
error_only_one_person: "Une seule personne peut être sélectionnée !"
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -38,6 +38,8 @@ class AccompanyingPeriodNormalizer implements NormalizerInterface, NormalizerAwa
|
||||
'remark' => $period->getRemark(),
|
||||
'participations' => $this->normalizer->normalize($period->getParticipations(), $format),
|
||||
'closingMotive' => $this->normalizer->normalize($period->getClosingMotive(), $format),
|
||||
'requestor' => $this->normalizer->normalize($period->getRequestor(), $format),
|
||||
'requestorAnonymous' => $this->normalizer->normalize($period->isRequestorAnonymous(), $format),
|
||||
'user' => $this->normalizer->normalize($period->getUser(), $format),
|
||||
'step' => $period->getStep(),
|
||||
'origin' => $this->normalizer->normalize($period->getOrigin(), $format),
|
||||
|
@ -25,7 +25,7 @@ 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;
|
||||
|
||||
/**
|
||||
* Serialize a Person entity
|
||||
@ -41,38 +41,58 @@ class PersonNormalizer implements
|
||||
|
||||
protected PersonRepository $repository;
|
||||
|
||||
private ChillEntityRenderExtension $render;
|
||||
|
||||
public const GET_PERSON = 'get_person';
|
||||
|
||||
public function __construct(PersonRepository $repository)
|
||||
public function __construct(PersonRepository $repository, ChillEntityRenderExtension $render)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->render = $render;
|
||||
}
|
||||
|
||||
public function normalize($person, string $format = null, array $context = array())
|
||||
{
|
||||
/** @var Person $person */
|
||||
return [
|
||||
'id' => $person->getId(),
|
||||
'type' => 'person',
|
||||
'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())
|
||||
];
|
||||
}
|
||||
|
||||
protected function normalizeAltNames($altNames): array
|
||||
{
|
||||
$r = [];
|
||||
|
||||
foreach ($altNames as $n) {
|
||||
$r[] = [ 'key' => $n->getKey(), 'label' => $n->getLabel() ];
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
public function denormalize($data, string $type, string $format = null, array $context = []): Person
|
||||
{
|
||||
if ($context[self::GET_PERSON] ?? true) {
|
||||
$id = $data['id'] ?? null;
|
||||
$id = $data['person_id'] ?? null;
|
||||
if (NULL === $id) {
|
||||
throw new RuntimeException("missing id into person object");
|
||||
throw new RuntimeException("missing person_id into person object");
|
||||
}
|
||||
}
|
||||
/** var Person $person */
|
||||
$person = $this->repository->findOneById($id);
|
||||
|
||||
if (NULL === $person) {
|
||||
return UnexpectedValueException("person id not found");
|
||||
throw new UnexpectedValueException("person id not found");
|
||||
}
|
||||
|
||||
return $person;
|
||||
|
@ -27,6 +27,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
@ -85,6 +86,116 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
$this->assertEquals(404, $response->getStatusCode(), "Test that the response of rest api has a status code 'not found' (404)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @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([ 'person_id' => $personId ])
|
||||
);
|
||||
$response = $this->client->getResponse();
|
||||
$data = \json_decode($response->getContent(), true);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertArrayHasKey('person_id', $data);
|
||||
$this->assertEquals($personId, $data['person_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([ 'thirdparty_id' => $thirdPartyId ])
|
||||
);
|
||||
$response = $this->client->getResponse();
|
||||
$data = \json_decode($response->getContent(), true);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertArrayHasKey('thirdparty_id', $data);
|
||||
$this->assertEquals($thirdPartyId, $data['thirdparty_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());
|
||||
}
|
||||
|
||||
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 +208,7 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
[], // parameters
|
||||
[], // files
|
||||
[], // server parameters
|
||||
\json_encode([ 'id' => $personId ])
|
||||
\json_encode([ 'person_id' => $personId ])
|
||||
);
|
||||
$response = $this->client->getResponse();
|
||||
$data = \json_decode($response->getContent(), true);
|
||||
@ -116,7 +227,7 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
|
||||
// check that the person id is contained
|
||||
$participationsPersonsIds = \array_map(
|
||||
function($participation) { return $participation->person->id; },
|
||||
function($participation) { return $participation->person->person_id; },
|
||||
$data->participations);
|
||||
|
||||
$this->assertContains($personId, $participationsPersonsIds);
|
||||
@ -128,7 +239,7 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
[], // parameters
|
||||
[], // files
|
||||
[], // server parameters
|
||||
\json_encode([ 'id' => $personId ])
|
||||
\json_encode([ 'person_id' => $personId ])
|
||||
);
|
||||
$response = $this->client->getResponse();
|
||||
$data = \json_decode($response->getContent(), true);
|
||||
@ -211,4 +322,5 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
$nbGenerated++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ namespace Chill\PersonBundle\Tests\Entity;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
|
||||
class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
@ -104,4 +105,30 @@ class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase
|
||||
$participation = $period->getOpenParticipationContainsPerson($person);
|
||||
$this->assertNull($participation);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
91
src/Bundle/ChillPersonBundle/chill.api.specs.yaml
Normal file
91
src/Bundle/ChillPersonBundle/chill.api.specs.yaml
Normal file
@ -0,0 +1,91 @@
|
||||
components:
|
||||
schemas:
|
||||
PersonById:
|
||||
type: object
|
||||
properties:
|
||||
person_id:
|
||||
type: integer
|
||||
required:
|
||||
- person_id
|
||||
ThirdPartyById:
|
||||
type: object
|
||||
properties:
|
||||
thirdparty_id:
|
||||
type: integer
|
||||
required:
|
||||
- thirdparty_id
|
||||
|
||||
|
||||
paths:
|
||||
/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"
|
||||
/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'
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
404:
|
||||
description: "Not found"
|
||||
200:
|
||||
description: "OK"
|
||||
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"
|
||||
|
@ -3,6 +3,7 @@ services:
|
||||
Chill\PersonBundle\Serializer\Normalizer\PersonNormalizer:
|
||||
arguments:
|
||||
$repository: '@Chill\PersonBundle\Repository\PersonRepository'
|
||||
$render: '@Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension'
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: 64 }
|
||||
|
||||
|
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\ThirdPartyBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\DataFixtures\ORM\LoadCenters;
|
||||
use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
|
||||
use Chill\MainBundle\Entity\PostalCode;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
||||
use Nelmio\Alice\Loader\NativeLoader;
|
||||
use Nelmio\Alice\ObjectSet;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class LoadThirdParty extends Fixture Implements DependentFixtureInterface
|
||||
{
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$thirdParties = $this->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' => '<fr_FR:streetName()>',
|
||||
'streetNumber' => '<fr_FR:buildingNumber()>',
|
||||
'validFrom' => '<dateTimeBetween(\'-1 year\', \'now\')>',
|
||||
'postCode' => $this->getPostalCode()
|
||||
],
|
||||
],
|
||||
ThirdParty::class => [
|
||||
'thirdparty{1..75}' => [
|
||||
'name' => '<fr_FR:company()>',
|
||||
'telephone' => '<fr_FR:phonenumber()>',
|
||||
'email' => '<email()>',
|
||||
'comment' => '<fr_FR:realTextBetween(10, 500)>',
|
||||
'address' => '@address<current()>'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
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' => '<fr_FR:company()>',
|
||||
'telephone' => '<fr_FR:phonenumber()>',
|
||||
'email' => '<email()>',
|
||||
'comment' => '<fr_FR:realTextBetween(10, 500)>'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
return $objectSet;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\ThirdPartyBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Exception\RuntimeException;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
|
||||
|
||||
|
||||
class ThirdPartyNormalizer implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface
|
||||
{
|
||||
private ThirdPartyRepository $repository;
|
||||
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
public function __construct(ThirdPartyRepository $repository)
|
||||
{
|
||||
$this->repository= $repository;
|
||||
}
|
||||
|
||||
public function normalize($thirdParty, string $format = null, array $context = [])
|
||||
{
|
||||
/** @var $thirdParty ThirdParty */
|
||||
$data['type'] = 'thirdparty';
|
||||
// TODO should be replaced by a "render entity"
|
||||
$data['text'] = $thirdParty->getName();
|
||||
$data['thirdparty_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;
|
||||
}
|
||||
|
||||
public function denormalize($data, string $type, string $format = null, array $context = [])
|
||||
{
|
||||
$thirdParty = $context[AbstractNormalizer::OBJECT_TO_POPULATE] ?? NULL;
|
||||
if (NULL === $thirdParty) {
|
||||
$id = $data['thirdparty_id'] ?? NULL;
|
||||
if (NULL === $id) {
|
||||
throw new RuntimeException("mission thirdparty_id into body");
|
||||
}
|
||||
|
||||
$thirdParty = $this->repository->findOneById($id);
|
||||
|
||||
if (NULL === $thirdParty) {
|
||||
throw new UnexpectedValueException("thirdparty not found");
|
||||
}
|
||||
}
|
||||
|
||||
return $thirdParty;
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, string $type, string $format = null)
|
||||
{
|
||||
return ThirdParty::class === $type;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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' }
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
services:
|
||||
Chill\ThirdPartyBundle\DataFixtures\ORM\LoadThirdParty:
|
||||
tags:
|
||||
- { 'name': doctrine.fixture.orm }
|
@ -0,0 +1 @@
|
||||
services:
|
Loading…
x
Reference in New Issue
Block a user