mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-10 08:44:58 +00:00
Compare commits
34 Commits
2.12.1
...
deploy/qui
Author | SHA1 | Date | |
---|---|---|---|
014170989e | |||
c26d342d30 | |||
2cf28b6c2d | |||
ed5351da14 | |||
8dca8d9b37 | |||
1cddad5c66 | |||
c98eda93cf | |||
dd991e3572 | |||
0640631821 | |||
e845d9ba90 | |||
|
6f8231f6f6 | ||
|
048161e300 | ||
|
4f49292178 | ||
357bf192b8 | |||
6eaffcae49 | |||
|
f92d710a26 | ||
|
bc6ba88acd | ||
efefb64119 | |||
6fa882f60f | |||
484058dcfc | |||
|
b72d45d9db | ||
2a1f5cbad1 | |||
e8566fd006 | |||
cd6b5c9a39 | |||
9aa3974071 | |||
a8bf478ee8 | |||
a35f3363b2 | |||
b7513068da | |||
2dd71ea197 | |||
757c064b7b | |||
f5d38f606c | |||
5abe482b12 | |||
5ded4822a2 | |||
6122a5d62f |
@@ -4,6 +4,7 @@ namespace Chill\ActivityBundle\Menu;
|
|||||||
|
|
||||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Knp\Menu\MenuItem;
|
use Knp\Menu\MenuItem;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
@@ -34,21 +35,13 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
{
|
{
|
||||||
$period = $parameters['accompanyingCourse'];
|
$period = $parameters['accompanyingCourse'];
|
||||||
|
|
||||||
$menu->addChild($this->translator->trans('Activity list'), [
|
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) {
|
||||||
'route' => 'chill_activity_activity_list',
|
$menu->addChild($this->translator->trans('Activity list'), [
|
||||||
'routeParameters' => [
|
'route' => 'chill_activity_activity_list',
|
||||||
'accompanying_period_id' => $period->getId(),
|
'routeParameters' => [
|
||||||
]])
|
'accompanying_period_id' => $period->getId(),
|
||||||
->setExtras(['order' => 40]);
|
]])
|
||||||
|
->setExtras(['order' => 40]);
|
||||||
$menu->addChild($this->translator->trans('Add a new activity'), [
|
}
|
||||||
'route' => 'chill_activity_activity_select_type',
|
|
||||||
'routeParameters' => [
|
|
||||||
'accompanying_period_id' => $period->getId(),
|
|
||||||
]])
|
|
||||||
->setExtras(['order' => 41]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,24 +55,26 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
{
|
{
|
||||||
/* @var $person \Chill\PersonBundle\Entity\Person */
|
/* @var $person \Chill\PersonBundle\Entity\Person */
|
||||||
$person = $parameters['person'];
|
$person = $parameters['person'];
|
||||||
|
|
||||||
if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) {
|
if (false) {
|
||||||
$menu->addChild(
|
if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) {
|
||||||
$this->translator->trans('Activity list'), [
|
$menu->addChild(
|
||||||
'route' => 'chill_activity_activity_list',
|
$this->translator->trans('Activity list'), [
|
||||||
'routeParameters' => [ 'person_id' => $person->getId() ],
|
'route' => 'chill_activity_activity_list',
|
||||||
])
|
'routeParameters' => [ 'person_id' => $person->getId() ],
|
||||||
->setExtra('order', 201)
|
])
|
||||||
;
|
->setExtra('order', 201)
|
||||||
}
|
;
|
||||||
if ($this->authorizationChecker->isGranted(ActivityVoter::CREATE, $person)) {
|
}
|
||||||
$menu->addChild(
|
if ($this->authorizationChecker->isGranted(ActivityVoter::CREATE, $person)) {
|
||||||
$this->translator->trans('Add a new activity'), [
|
$menu->addChild(
|
||||||
'route' => 'chill_activity_activity_new',
|
$this->translator->trans('Add a new activity'), [
|
||||||
'routeParameters' => [ 'person_id' => $person->getId() ],
|
'route' => 'chill_activity_activity_new',
|
||||||
])
|
'routeParameters' => [ 'person_id' => $person->getId() ],
|
||||||
->setExtra('order', 200)
|
])
|
||||||
;
|
->setExtra('order', 200)
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,6 +36,7 @@ use Chill\MainBundle\Search\SearchProvider;
|
|||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Search\SearchApi;
|
use Chill\MainBundle\Search\SearchApi;
|
||||||
|
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SearchController
|
* Class SearchController
|
||||||
@@ -151,11 +152,14 @@ class SearchController extends AbstractController
|
|||||||
{
|
{
|
||||||
//TODO this is an incomplete implementation
|
//TODO this is an incomplete implementation
|
||||||
$query = $request->query->get('q', '');
|
$query = $request->query->get('q', '');
|
||||||
|
$types = $request->query->get('type', []);
|
||||||
|
|
||||||
$results = $this->searchApi->getResults($query, 0, 150);
|
if (count($types) === 0) {
|
||||||
$paginator = $this->paginatorFactory->create(count($results));
|
throw new BadRequestException("The request must contains at "
|
||||||
|
." one type");
|
||||||
|
}
|
||||||
|
|
||||||
$collection = new Collection($results, $paginator);
|
$collection = $this->searchApi->getResults($query, $types, []);
|
||||||
|
|
||||||
return $this->json($collection);
|
return $this->json($collection);
|
||||||
}
|
}
|
||||||
|
@@ -132,6 +132,7 @@ class Address
|
|||||||
/**
|
/**
|
||||||
* True if the address is a "no address", aka homeless person, ...
|
* True if the address is a "no address", aka homeless person, ...
|
||||||
* @groups({"write"})
|
* @groups({"write"})
|
||||||
|
* @ORM\Column(type="boolean")
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
@@ -298,7 +299,7 @@ class Address
|
|||||||
* @param bool $isNoAddress
|
* @param bool $isNoAddress
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setIsNoAddress(bool $isNoAddress)
|
public function setIsNoAddress(bool $isNoAddress): self
|
||||||
{
|
{
|
||||||
$this->isNoAddress = $isNoAddress;
|
$this->isNoAddress = $isNoAddress;
|
||||||
return $this;
|
return $this;
|
||||||
|
@@ -2,88 +2,169 @@
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Search;
|
namespace Chill\MainBundle\Search;
|
||||||
|
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
use Chill\PersonBundle\Search\SearchPersonApiProvider;
|
||||||
|
use Chill\ThirdPartyBundle\Search\ThirdPartyApiSearch;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
use Chill\MainBundle\Search\SearchProvider;
|
use Chill\MainBundle\Search\SearchProvider;
|
||||||
use Symfony\Component\VarDumper\Resources\functions\dump;
|
use Symfony\Component\VarDumper\Resources\functions\dump;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ***Warning*** This is an incomplete implementation ***Warning***
|
|
||||||
*/
|
*/
|
||||||
class SearchApi
|
class SearchApi
|
||||||
{
|
{
|
||||||
private EntityManagerInterface $em;
|
private EntityManagerInterface $em;
|
||||||
private SearchProvider $search;
|
private PaginatorFactory $paginator;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $em, SearchProvider $search)
|
private array $providers = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
EntityManagerInterface $em,
|
||||||
|
SearchPersonApiProvider $searchPerson,
|
||||||
|
ThirdPartyApiSearch $thirdPartyApiSearch,
|
||||||
|
PaginatorFactory $paginator
|
||||||
|
)
|
||||||
{
|
{
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
$this->search = $search;
|
$this->providers[] = $searchPerson;
|
||||||
|
$this->providers[] = $thirdPartyApiSearch;
|
||||||
|
$this->paginator = $paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Model/Result[]
|
* @return Model/Result[]
|
||||||
*/
|
*/
|
||||||
public function getResults(string $query, int $offset, int $maxResult): array
|
public function getResults(string $pattern, array $types, array $parameters): Collection
|
||||||
{
|
{
|
||||||
// **warning again**: this is an incomplete implementation
|
$queries = $this->findQueries($pattern, $types, $parameters);
|
||||||
$results = [];
|
|
||||||
|
|
||||||
foreach ($this->getPersons($query) as $p) {
|
$total = $this->countItems($queries, $types, $parameters);
|
||||||
$results[] = new Model\Result((float)\rand(0, 100) / 100, $p);
|
$paginator = $this->paginator->create($total);
|
||||||
}
|
|
||||||
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) {
|
$rawResults = $this->fetchRawResult($queries, $types, $parameters, $paginator);
|
||||||
return ($a->getRelevance() <=> $b->getRelevance()) * -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
return $results;
|
$this->prepareProviders($rawResults);
|
||||||
|
$results = $this->buildResults($rawResults);
|
||||||
|
|
||||||
|
$collection = new Collection($results, $paginator);
|
||||||
|
|
||||||
|
return $collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countResults(string $query): int
|
private function findQueries($pattern, array $types, array $parameters): array
|
||||||
{
|
{
|
||||||
return 0;
|
return \array_map(
|
||||||
|
fn($p) => $p->provideQuery($pattern, $parameters),
|
||||||
|
$this->findProviders($pattern, $types, $parameters),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getThirdParties(string $query)
|
private function findProviders(string $pattern, array $types, array $parameters): array
|
||||||
{
|
{
|
||||||
$thirdPartiesIds = $this->em->createQuery('SELECT t.id FROM '.ThirdParty::class.' t')
|
return \array_filter(
|
||||||
->getScalarResult();
|
$this->providers,
|
||||||
$nbResults = rand(0, 15);
|
fn($p) => $p->supportsTypes($pattern, $types, $parameters)
|
||||||
|
);
|
||||||
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)
|
private function countItems($providers, $types, $parameters): int
|
||||||
{
|
{
|
||||||
$params = [
|
list($countQuery, $parameters) = $this->buildCountQuery($providers, $types, $parameters);
|
||||||
SearchInterface::SEARCH_PREVIEW_OPTION => false
|
$rsmCount = new ResultSetMappingBuilder($this->em);
|
||||||
];
|
$rsmCount->addScalarResult('count', 'count');
|
||||||
$search = $this->search->getResultByName($query, 'person_regular', 0, 50, $params, 'json');
|
$countNq = $this->em->createNativeQuery($countQuery, $rsmCount);
|
||||||
$ids = \array_map(function($r) { return $r['id']; }, $search['results']);
|
$countNq->setParameters($parameters);
|
||||||
|
|
||||||
|
return $countNq->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCountQuery(array $queries, $types, $parameters)
|
||||||
|
{
|
||||||
|
$query = "SELECT COUNT(sq.key) AS count FROM ({union_unordered}) AS sq";
|
||||||
|
$unions = [];
|
||||||
|
$parameters = [];
|
||||||
|
|
||||||
if (count($ids) === 0) {
|
foreach ($queries as $q) {
|
||||||
return [];
|
$unions[] = $q->buildQuery();
|
||||||
|
$parameters = \array_merge($parameters, $q->buildParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->em->getRepository(Person::class)
|
$unionUnordered = \implode(" UNION ", $unions);
|
||||||
->findById($ids)
|
|
||||||
|
return [
|
||||||
|
\strtr($query, [ '{union_unordered}' => $unionUnordered ]),
|
||||||
|
$parameters
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildUnionQuery(array $queries, $types, $parameters)
|
||||||
|
{
|
||||||
|
$query = "{unions} ORDER BY pertinence DESC";
|
||||||
|
$unions = [];
|
||||||
|
$parameters = [];
|
||||||
|
|
||||||
|
foreach ($queries as $q) {
|
||||||
|
$unions[] = $q->buildQuery();
|
||||||
|
$parameters = \array_merge($parameters, $q->buildParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
$union = \implode(" UNION ", $unions);
|
||||||
|
|
||||||
|
return [
|
||||||
|
\strtr($query, [ '{unions}' => $union]),
|
||||||
|
$parameters
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fetchRawResult($queries, $types, $parameters, $paginator): array
|
||||||
|
{
|
||||||
|
list($union, $parameters) = $this->buildUnionQuery($queries, $types, $parameters, $paginator);
|
||||||
|
$rsm = new ResultSetMappingBuilder($this->em);
|
||||||
|
$rsm->addScalarResult('key', 'key', Types::STRING)
|
||||||
|
->addScalarResult('metadata', 'metadata', Types::JSON)
|
||||||
|
->addScalarResult('pertinence', 'pertinence', Types::FLOAT)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
$nq = $this->em->createNativeQuery($union, $rsm);
|
||||||
|
$nq->setParameters($parameters);
|
||||||
|
|
||||||
|
return $nq->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prepareProviders($rawResults)
|
||||||
|
{
|
||||||
|
$metadatas = [];
|
||||||
|
foreach ($rawResults as $r) {
|
||||||
|
foreach ($this->providers as $k => $p) {
|
||||||
|
if ($p->supportsResult($r['key'], $r['metadata'])) {
|
||||||
|
$metadatas[$k][] = $r['metadata'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($metadatas as $k => $m) {
|
||||||
|
$this->providers[$k]->prepare($m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildResults($rawResults)
|
||||||
|
{
|
||||||
|
foreach ($rawResults as $r) {
|
||||||
|
foreach ($this->providers as $k => $p) {
|
||||||
|
if ($p->supportsResult($r['key'], $r['metadata'])) {
|
||||||
|
$items[] = (new SearchApiResult($r['pertinence']))
|
||||||
|
->setResult(
|
||||||
|
$p->getResult($r['key'], $r['metadata'], $r['pertinence'])
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $items ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
18
src/Bundle/ChillMainBundle/Search/SearchApiInterface.php
Normal file
18
src/Bundle/ChillMainBundle/Search/SearchApiInterface.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Search;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Search\SearchApiQuery;
|
||||||
|
|
||||||
|
interface SearchApiInterface
|
||||||
|
{
|
||||||
|
public function provideQuery(string $pattern, array $parameters): SearchApiQuery;
|
||||||
|
|
||||||
|
public function supportsTypes(string $pattern, array $types, array $parameters): bool;
|
||||||
|
|
||||||
|
public function prepare(array $metadatas): void;
|
||||||
|
|
||||||
|
public function supportsResult(string $key, array $metadatas): bool;
|
||||||
|
|
||||||
|
public function getResult(string $key, array $metadata, float $pertinence);
|
||||||
|
}
|
85
src/Bundle/ChillMainBundle/Search/SearchApiQuery.php
Normal file
85
src/Bundle/ChillMainBundle/Search/SearchApiQuery.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Search;
|
||||||
|
|
||||||
|
class SearchApiQuery
|
||||||
|
{
|
||||||
|
private ?string $selectKey;
|
||||||
|
private array $selectKeyParams = [];
|
||||||
|
private ?string $jsonbMetadata;
|
||||||
|
private array $jsonbMetadataParams = [];
|
||||||
|
private ?string $pertinence;
|
||||||
|
private array $pertinenceParams = [];
|
||||||
|
private ?string $fromClause;
|
||||||
|
private array $fromClauseParams = [];
|
||||||
|
private ?string $whereClause;
|
||||||
|
private array $whereClauseParams = [];
|
||||||
|
|
||||||
|
public function setSelectKey(string $selectKey, array $params = []): self
|
||||||
|
{
|
||||||
|
$this->selectKey = $selectKey;
|
||||||
|
$this->selectKeyParams = $params;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSelectJsonbMetadata(string $jsonbMetadata, array $params = []): self
|
||||||
|
{
|
||||||
|
$this->jsonbMetadata = $jsonbMetadata;
|
||||||
|
$this->jsonbMetadataParams = $params;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSelectPertinence(string $pertinence, array $params = []): self
|
||||||
|
{
|
||||||
|
$this->pertinence = $pertinence;
|
||||||
|
$this->pertinenceParams = $params;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFromClause(string $fromClause, array $params = []): self
|
||||||
|
{
|
||||||
|
$this->fromClause = $fromClause;
|
||||||
|
$this->fromClauseParams = $params;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWhereClause(string $whereClause, array $params = []): self
|
||||||
|
{
|
||||||
|
$this->whereClause = $whereClause;
|
||||||
|
$this->whereClauseParams = $params;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildQuery(): string
|
||||||
|
{
|
||||||
|
return \strtr("SELECT
|
||||||
|
'{key}' AS key,
|
||||||
|
{metadata} AS metadata,
|
||||||
|
{pertinence} AS pertinence
|
||||||
|
FROM {from}
|
||||||
|
WHERE {where}
|
||||||
|
", [
|
||||||
|
'{key}' => $this->selectKey,
|
||||||
|
'{metadata}' => $this->jsonbMetadata,
|
||||||
|
'{pertinence}' => $this->pertinence,
|
||||||
|
'{from}' => $this->fromClause,
|
||||||
|
'{where}' => $this->whereClause
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildParameters(): array
|
||||||
|
{
|
||||||
|
return \array_merge(
|
||||||
|
$this->selectKeyParams,
|
||||||
|
$this->jsonbMetadataParams,
|
||||||
|
$this->pertinenceParams,
|
||||||
|
$this->fromClauseParams,
|
||||||
|
$this->whereClauseParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
src/Bundle/ChillMainBundle/Search/SearchApiResult.php
Normal file
40
src/Bundle/ChillMainBundle/Search/SearchApiResult.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Search;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
|
class SearchApiResult
|
||||||
|
{
|
||||||
|
private float $pertinence;
|
||||||
|
|
||||||
|
private $result;
|
||||||
|
|
||||||
|
public function __construct(float $relevance)
|
||||||
|
{
|
||||||
|
$this->relevance = $relevance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setResult($result): self
|
||||||
|
{
|
||||||
|
$this->result = $result;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Serializer\Groups({"read"})
|
||||||
|
*/
|
||||||
|
public function getResult()
|
||||||
|
{
|
||||||
|
return $this->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Serializer\Groups({"read"})
|
||||||
|
*/
|
||||||
|
public function getRelevance(): float
|
||||||
|
{
|
||||||
|
return $this->relevance;
|
||||||
|
}
|
||||||
|
}
|
@@ -14,7 +14,7 @@ class AddressNormalizer implements NormalizerAwareInterface, NormalizerInterface
|
|||||||
public function normalize($address, string $format = null, array $context = [])
|
public function normalize($address, string $format = null, array $context = [])
|
||||||
{
|
{
|
||||||
$data['address_id'] = $address->getId();
|
$data['address_id'] = $address->getId();
|
||||||
$data['text'] = $address->getStreet().', '.$address->getStreetNumber();
|
$data['text'] = $address->isNoAddress() ? '' : $address->getStreet().', '.$address->getStreetNumber();
|
||||||
$data['street'] = $address->getStreet();
|
$data['street'] = $address->getStreet();
|
||||||
$data['streetNumber'] = $address->getStreetNumber();
|
$data['streetNumber'] = $address->getStreetNumber();
|
||||||
$data['postcode']['name'] = $address->getPostCode()->getName();
|
$data['postcode']['name'] = $address->getPostCode()->getName();
|
||||||
|
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bundle\ChillMainBundle\Tests\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
class SearchApiControllerTest extends WebTestCase
|
||||||
|
{
|
||||||
|
use PrepareClientTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider generateSearchData
|
||||||
|
*/
|
||||||
|
public function testSearch(string $pattern, array $types)
|
||||||
|
{
|
||||||
|
$client = $this->getClientAuthenticated();
|
||||||
|
|
||||||
|
$client->request(
|
||||||
|
Request::METHOD_GET,
|
||||||
|
'/api/1.0/search.json',
|
||||||
|
[ 'q' => $pattern, 'type' => $types ]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateSearchData()
|
||||||
|
{
|
||||||
|
yield ['per', ['person', 'thirdparty'] ];
|
||||||
|
yield ['per', ['thirdparty'] ];
|
||||||
|
yield ['per', ['person'] ];
|
||||||
|
yield ['fjklmeqjfkdqjklrmefdqjklm', ['person', 'thirdparty'] ];
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,5 @@ services:
|
|||||||
Chill\MainBundle\Search\SearchProvider: '@chill_main.search_provider'
|
Chill\MainBundle\Search\SearchProvider: '@chill_main.search_provider'
|
||||||
|
|
||||||
Chill\MainBundle\Search\SearchApi:
|
Chill\MainBundle\Search\SearchApi:
|
||||||
arguments:
|
autowire: true
|
||||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
autoconfigure: true
|
||||||
$search: '@Chill\MainBundle\Search\SearchProvider'
|
|
||||||
|
@@ -7,6 +7,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
|||||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||||
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
|
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
@@ -35,14 +36,18 @@ class AccompanyingCourseController extends Controller
|
|||||||
|
|
||||||
protected ValidatorInterface $validator;
|
protected ValidatorInterface $validator;
|
||||||
|
|
||||||
|
private AccompanyingPeriodWorkRepository $workRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
SerializerInterface $serializer,
|
SerializerInterface $serializer,
|
||||||
EventDispatcherInterface $dispatcher,
|
EventDispatcherInterface $dispatcher,
|
||||||
ValidatorInterface $validator
|
ValidatorInterface $validator,
|
||||||
|
AccompanyingPeriodWorkRepository $workRepository
|
||||||
) {
|
) {
|
||||||
$this->serializer = $serializer;
|
$this->serializer = $serializer;
|
||||||
$this->dispatcher = $dispatcher;
|
$this->dispatcher = $dispatcher;
|
||||||
$this->validator = $validator;
|
$this->validator = $validator;
|
||||||
|
$this->workRepository = $workRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,9 +107,16 @@ class AccompanyingCourseController extends Controller
|
|||||||
['date' => 'DESC'],
|
['date' => 'DESC'],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$works = $this->workRepository->findByAccompanyingPeriod(
|
||||||
|
$accompanyingCourse,
|
||||||
|
['startDate' => 'DESC', 'endDate' => 'DESC'],
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [
|
return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [
|
||||||
'accompanyingCourse' => $accompanyingCourse,
|
'accompanyingCourse' => $accompanyingCourse,
|
||||||
'withoutHousehold' => $withoutHousehold,
|
'withoutHousehold' => $withoutHousehold,
|
||||||
|
'works' => $works,
|
||||||
'activities' => $activities
|
'activities' => $activities
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -47,12 +47,12 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
* @var EventDispatcherInterface
|
* @var EventDispatcherInterface
|
||||||
*/
|
*/
|
||||||
protected $eventDispatcher;
|
protected $eventDispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ValidatorInterface
|
* @var ValidatorInterface
|
||||||
*/
|
*/
|
||||||
protected $validator;
|
protected $validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AccompanyingPeriodController constructor.
|
* AccompanyingPeriodController constructor.
|
||||||
*
|
*
|
||||||
@@ -64,23 +64,25 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
$this->eventDispatcher = $eventDispatcher;
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
$this->validator = $validator;
|
$this->validator = $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listAction(int $person_id): Response
|
public function listAction(int $person_id): Response
|
||||||
{
|
{
|
||||||
$person = $this->_getPerson($person_id);
|
$person = $this->_getPerson($person_id);
|
||||||
|
|
||||||
$event = new PrivacyEvent($person, [
|
$event = new PrivacyEvent($person, [
|
||||||
'element_class' => AccompanyingPeriod::class,
|
'element_class' => AccompanyingPeriod::class,
|
||||||
'action' => 'list'
|
'action' => 'list'
|
||||||
]);
|
]);
|
||||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||||
|
|
||||||
|
$accompanyingPeriods = $person->getAccompanyingPeriodsOrdered();
|
||||||
|
|
||||||
return $this->render('ChillPersonBundle:AccompanyingPeriod:list.html.twig', [
|
return $this->render('ChillPersonBundle:AccompanyingPeriod:list.html.twig', [
|
||||||
'accompanying_periods' => $person->getAccompanyingPeriodsOrdered(),
|
'accompanying_periods' => $accompanyingPeriods,
|
||||||
'person' => $person
|
'person' => $person
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createAction(int $person_id, Request $request): Response
|
public function createAction(int $person_id, Request $request): Response
|
||||||
{
|
{
|
||||||
$person = $this->_getPerson($person_id);
|
$person = $this->_getPerson($person_id);
|
||||||
@@ -90,17 +92,17 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
|
|
||||||
$accompanyingPeriod = new AccompanyingPeriod(new \DateTime('now'));
|
$accompanyingPeriod = new AccompanyingPeriod(new \DateTime('now'));
|
||||||
$accompanyingPeriod->setClosingDate(new \DateTime('now'));
|
$accompanyingPeriod->setClosingDate(new \DateTime('now'));
|
||||||
|
|
||||||
$accompanyingPeriod->addPerson($person);
|
$accompanyingPeriod->addPerson($person);
|
||||||
//or $person->addAccompanyingPeriod($accompanyingPeriod);
|
//or $person->addAccompanyingPeriod($accompanyingPeriod);
|
||||||
|
|
||||||
$form = $this->createForm(
|
$form = $this->createForm(
|
||||||
AccompanyingPeriodType::class,
|
AccompanyingPeriodType::class,
|
||||||
$accompanyingPeriod, [
|
$accompanyingPeriod, [
|
||||||
'period_action' => 'create',
|
'period_action' => 'create',
|
||||||
'center' => $person->getCenter()
|
'center' => $person->getCenter()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($request->getMethod() === 'POST') {
|
if ($request->getMethod() === 'POST') {
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
$errors = $this->_validatePerson($person);
|
$errors = $this->_validatePerson($person);
|
||||||
@@ -120,7 +122,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
$this->generateUrl('chill_person_accompanying_period_list', [
|
$this->generateUrl('chill_person_accompanying_period_list', [
|
||||||
'person_id' => $person->getId()
|
'person_id' => $person->getId()
|
||||||
]));
|
]));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$flashBag->add('error', $this->get('translator')
|
$flashBag->add('error', $this->get('translator')
|
||||||
->trans('Error! Period not created!'));
|
->trans('Error! Period not created!'));
|
||||||
@@ -137,7 +139,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
'accompanying_period' => $accompanyingPeriod
|
'accompanying_period' => $accompanyingPeriod
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
@@ -154,7 +156,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
|
|
||||||
/** @var Person $person */
|
/** @var Person $person */
|
||||||
$person = $this->_getPerson($person_id);
|
$person = $this->_getPerson($person_id);
|
||||||
|
|
||||||
// CHECK
|
// CHECK
|
||||||
if (! $accompanyingPeriod->containsPerson($person)) {
|
if (! $accompanyingPeriod->containsPerson($person)) {
|
||||||
throw new Exception("Accompanying period " . $period_id . " does not contain person " . $person_id);
|
throw new Exception("Accompanying period " . $period_id . " does not contain person " . $person_id);
|
||||||
@@ -176,7 +178,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
|
|
||||||
if ($form->isValid(['Default', 'closed'])
|
if ($form->isValid(['Default', 'closed'])
|
||||||
&& count($errors) === 0) {
|
&& count($errors) === 0) {
|
||||||
|
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$flashBag->add('success',
|
$flashBag->add('success',
|
||||||
@@ -186,9 +188,9 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
$this->generateUrl('chill_person_accompanying_period_list', [
|
$this->generateUrl('chill_person_accompanying_period_list', [
|
||||||
'person_id' => $person->getId()
|
'person_id' => $person->getId()
|
||||||
]));
|
]));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$flashBag->add('error', $this->get('translator')
|
$flashBag->add('error', $this->get('translator')
|
||||||
->trans('Error when updating the period'));
|
->trans('Error when updating the period'));
|
||||||
|
|
||||||
@@ -204,19 +206,19 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
'accompanying_period' => $accompanyingPeriod
|
'accompanying_period' => $accompanyingPeriod
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function closeAction(int $person_id, Request $request): Response
|
public function closeAction(int $person_id, Request $request): Response
|
||||||
{
|
{
|
||||||
|
|
||||||
$person = $this->_getPerson($person_id);
|
$person = $this->_getPerson($person_id);
|
||||||
|
|
||||||
$this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, 'You are not allowed to update this person');
|
$this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, 'You are not allowed to update this person');
|
||||||
|
|
||||||
if ($person->isOpen() === false) {
|
if ($person->isOpen() === false) {
|
||||||
|
|
||||||
$this->get('session')->getFlashBag()
|
$this->get('session')->getFlashBag()
|
||||||
->add('error', $this->get('translator')
|
->add('error', $this->get('translator')
|
||||||
->trans('Beware period is closed', ['%name%' => $person->__toString()]
|
->trans('Beware period is closed', ['%name%' => $person->__toString()]
|
||||||
@@ -229,7 +231,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$current = $person->getCurrentAccompanyingPeriod();
|
$current = $person->getCurrentAccompanyingPeriod();
|
||||||
|
|
||||||
$form = $this->createForm(AccompanyingPeriodType::class, $current, [
|
$form = $this->createForm(AccompanyingPeriodType::class, $current, [
|
||||||
'period_action' => 'close',
|
'period_action' => 'close',
|
||||||
'center' => $person->getCenter()
|
'center' => $person->getCenter()
|
||||||
@@ -256,7 +258,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
'person_id' => $person->getId()
|
'person_id' => $person->getId()
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$this->get('session')->getFlashBag()
|
$this->get('session')->getFlashBag()
|
||||||
->add('error', $this->get('translator')
|
->add('error', $this->get('translator')
|
||||||
@@ -267,7 +269,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
->add('info', $error->getMessage());
|
->add('info', $error->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { //if form is not valid
|
} else { //if form is not valid
|
||||||
$this->get('session')->getFlashBag()
|
$this->get('session')->getFlashBag()
|
||||||
->add('error',
|
->add('error',
|
||||||
@@ -288,7 +290,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
'accompanying_period' => $current
|
'accompanying_period' => $current
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function _validatePerson(Person $person): ConstraintViolationListInterface
|
private function _validatePerson(Person $person): ConstraintViolationListInterface
|
||||||
{
|
{
|
||||||
$errors = $this->validator->validate($person, null,
|
$errors = $this->validator->validate($person, null,
|
||||||
@@ -296,10 +298,10 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
|
|
||||||
// Can be disabled with config
|
// Can be disabled with config
|
||||||
if (false === $this->container->getParameter('chill_person.allow_multiple_simultaneous_accompanying_periods')) {
|
if (false === $this->container->getParameter('chill_person.allow_multiple_simultaneous_accompanying_periods')) {
|
||||||
|
|
||||||
$errors_accompanying_period = $this->validator->validate($person, null,
|
$errors_accompanying_period = $this->validator->validate($person, null,
|
||||||
['accompanying_period_consistent']);
|
['accompanying_period_consistent']);
|
||||||
|
|
||||||
foreach($errors_accompanying_period as $error ) {
|
foreach($errors_accompanying_period as $error ) {
|
||||||
$errors->add($error);
|
$errors->add($error);
|
||||||
}
|
}
|
||||||
@@ -307,7 +309,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
|
|
||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function openAction(int $person_id, Request $request): Response
|
public function openAction(int $person_id, Request $request): Response
|
||||||
{
|
{
|
||||||
$person = $this->_getPerson($person_id);
|
$person = $this->_getPerson($person_id);
|
||||||
@@ -384,7 +386,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
'accompanying_period' => $accompanyingPeriod
|
'accompanying_period' => $accompanyingPeriod
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reOpenAction(int $person_id, int $period_id, Request $request): Response
|
public function reOpenAction(int $person_id, int $period_id, Request $request): Response
|
||||||
{
|
{
|
||||||
/** @var Person $person */
|
/** @var Person $person */
|
||||||
@@ -392,7 +394,7 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
|
|
||||||
/* @var $period AccompanyingPeriod */
|
/* @var $period AccompanyingPeriod */
|
||||||
$period = \array_filter(
|
$period = \array_filter(
|
||||||
$person->getAccompanyingPeriods(),
|
$person->getAccompanyingPeriods(),
|
||||||
function (AccompanyingPeriod $p) use ($period_id) {
|
function (AccompanyingPeriod $p) use ($period_id) {
|
||||||
return $p->getId() === ($period_id);
|
return $p->getId() === ($period_id);
|
||||||
}
|
}
|
||||||
@@ -417,13 +419,13 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
return $this->redirectToRoute('chill_person_accompanying_period_list', [
|
return $this->redirectToRoute('chill_person_accompanying_period_list', [
|
||||||
'person_id' => $person->getId()
|
'person_id' => $person->getId()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} elseif ($confirm === false && $period->canBeReOpened($person)) {
|
} elseif ($confirm === false && $period->canBeReOpened($person)) {
|
||||||
return $this->render('ChillPersonBundle:AccompanyingPeriod:re_open.html.twig', [
|
return $this->render('ChillPersonBundle:AccompanyingPeriod:re_open.html.twig', [
|
||||||
'period' => $period,
|
'period' => $period,
|
||||||
'person' => $person
|
'person' => $person
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return (new Response())
|
return (new Response())
|
||||||
->setStatusCode(Response::HTTP_BAD_REQUEST)
|
->setStatusCode(Response::HTTP_BAD_REQUEST)
|
||||||
|
@@ -4,15 +4,50 @@ namespace Chill\PersonBundle\Controller;
|
|||||||
|
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
use Chill\MainBundle\Entity\Address;
|
use Chill\MainBundle\Entity\Address;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Repository\Household\HouseholdRepository;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||||
|
|
||||||
class HouseholdApiController extends ApiController
|
class HouseholdApiController extends ApiController
|
||||||
{
|
{
|
||||||
|
private HouseholdRepository $householdRepository;
|
||||||
|
|
||||||
|
public function __construct(HouseholdRepository $householdRepository)
|
||||||
|
{
|
||||||
|
$this->householdRepository = $householdRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function householdAddressApi($id, Request $request, string $_format): Response
|
public function householdAddressApi($id, Request $request, string $_format): Response
|
||||||
{
|
{
|
||||||
return $this->addRemoveSomething('address', $id, $request, $_format, 'address', Address::class, [ 'groups' => [ 'read' ] ]);
|
return $this->addRemoveSomething('address', $id, $request, $_format, 'address', Address::class, [ 'groups' => [ 'read' ] ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find Household of people participating to the same AccompanyingPeriod
|
||||||
|
*
|
||||||
|
* @ParamConverter("person", options={"id" = "person_id"})
|
||||||
|
*/
|
||||||
|
public function suggestHouseholdByAccompanyingPeriodParticipationApi(Person $person, string $_format)
|
||||||
|
{
|
||||||
|
// TODO add acl
|
||||||
|
|
||||||
|
$count = $this->householdRepository->countByAccompanyingPeriodParticipation($person);
|
||||||
|
$paginator = $this->getPaginatorFactory()->create($count);
|
||||||
|
|
||||||
|
if ($count === 0) {
|
||||||
|
$households = [];
|
||||||
|
} else {
|
||||||
|
$households = $this->householdRepository->findByAccompanyingPeriodParticipation($person,
|
||||||
|
$paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
$collection = new Collection($households, $paginator);
|
||||||
|
|
||||||
|
return $this->json($collection, Response::HTTP_OK, [],
|
||||||
|
[ "groups" => ["read"]]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
namespace Chill\PersonBundle\Controller;
|
namespace Chill\PersonBundle\Controller;
|
||||||
|
|
||||||
use Chill\PersonBundle\Entity\Household\Position;
|
use Chill\PersonBundle\Entity\Household\Position;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
|
||||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Entity\Household\Household;
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
@@ -25,11 +26,17 @@ class HouseholdMemberController extends ApiController
|
|||||||
|
|
||||||
private TranslatorInterface $translator;
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
|
private AccompanyingPeriodRepository $periodRepository;
|
||||||
|
|
||||||
public function __construct(UrlGeneratorInterface $generator, TranslatorInterface $translator)
|
public function __construct(
|
||||||
|
UrlGeneratorInterface $generator,
|
||||||
|
TranslatorInterface $translator,
|
||||||
|
AccompanyingPeriodRepository $periodRepository
|
||||||
|
)
|
||||||
{
|
{
|
||||||
$this->generator = $generator;
|
$this->generator = $generator;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
|
$this->periodRepository = $periodRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,8 +151,23 @@ class HouseholdMemberController extends ApiController
|
|||||||
'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'),
|
'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// context
|
||||||
|
if ($request->query->has('accompanying_period_id')) {
|
||||||
|
$period = $this->periodRepository->find(
|
||||||
|
$request->query->getInt('accompanying_period_id')
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($period === null) {
|
||||||
|
throw $this->createNotFoundException('period not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add acl on accompanying Course
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('@ChillPerson/Household/members_editor.html.twig', [
|
return $this->render('@ChillPerson/Household/members_editor.html.twig', [
|
||||||
'data' => $data
|
'data' => $data,
|
||||||
|
'expandSuggestions' => (int) $request->query->getBoolean('expand_suggestions', false),
|
||||||
|
'accompanyingCourse' => $period ?? null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -565,6 +565,13 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
|||||||
],
|
],
|
||||||
'controller_action' => 'householdAddressApi'
|
'controller_action' => 'householdAddressApi'
|
||||||
],
|
],
|
||||||
|
'suggestHouseholdByAccompanyingPeriodParticipation' => [
|
||||||
|
'path' => '/suggest/by-person/{person_id}/through-accompanying-period-participation.{_format}',
|
||||||
|
'methods' => [
|
||||||
|
Request::METHOD_GET => true,
|
||||||
|
Request::METHOD_HEAD => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@@ -25,8 +25,9 @@ namespace Chill\PersonBundle\Repository;
|
|||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
final class AccompanyingPeriodRepository
|
final class AccompanyingPeriodRepository implements ObjectRepository
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
@@ -34,4 +35,32 @@ final class AccompanyingPeriodRepository
|
|||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(AccompanyingPeriod::class);
|
$this->repository = $entityManager->getRepository(AccompanyingPeriod::class);
|
||||||
}
|
}
|
||||||
|
public function find($id): ?AccompanyingPeriod
|
||||||
|
{
|
||||||
|
return $this->repository->find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return AccompanyingPeriod[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): ?AccompanyingPeriod
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?AccompanyingPeriod
|
||||||
|
{
|
||||||
|
return $this->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return AccompanyingPeriod::class;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,15 +3,102 @@
|
|||||||
namespace Chill\PersonBundle\Repository\Household;
|
namespace Chill\PersonBundle\Repository\Household;
|
||||||
|
|
||||||
use Chill\PersonBundle\Entity\Household\Household;
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
final class HouseholdRepository
|
final class HouseholdRepository implements ObjectRepository
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(Household::class);
|
$this->repository = $entityManager->getRepository(Household::class);
|
||||||
|
$this->em = $entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function find($id)
|
||||||
|
{
|
||||||
|
return $this->repository->find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAll()
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria)
|
||||||
|
{
|
||||||
|
return $this->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return Household::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countByAccompanyingPeriodParticipation(Person $person)
|
||||||
|
{
|
||||||
|
return $this->buildQueryByAccompanyingPeriodParticipation($person, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByAccompanyingPeriodParticipation(Person $person, int $limit, int $offset)
|
||||||
|
{
|
||||||
|
return $this->buildQueryByAccompanyingPeriodParticipation($person, false, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildQueryByAccompanyingPeriodParticipation(Person $person, bool $isCount = false, int $limit = 50, int $offset = 0)
|
||||||
|
{
|
||||||
|
$rsm = new ResultSetMappingBuilder($this->em);
|
||||||
|
$rsm->addRootEntityFromClassMetadata(Household::class, 'h');
|
||||||
|
|
||||||
|
if ($isCount) {
|
||||||
|
$rsm->addScalarResult('count', 'count');
|
||||||
|
$sql = \strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [
|
||||||
|
'{select}' => 'COUNT(households.*) AS count',
|
||||||
|
'{limits}' => ''
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$sql = \strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [
|
||||||
|
'{select}' => $rsm->generateSelectClause(['h' => 'households']),
|
||||||
|
'{limits}' => "OFFSET {$offset} LIMIT {$limit}"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$native = $this->em->createNativeQuery($sql, $rsm);
|
||||||
|
$native->setParameters([0 => $person->getId(), 1 => $person->getId()]);
|
||||||
|
|
||||||
|
if ($isCount) {
|
||||||
|
return $native->getSingleScalarResult();
|
||||||
|
} else {
|
||||||
|
return $native->getResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CONST SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION = <<<SQL
|
||||||
|
WITH participations AS (
|
||||||
|
SELECT DISTINCT part.accompanyingperiod_id
|
||||||
|
FROM chill_person_accompanying_period_participation AS part
|
||||||
|
WHERE person_id = ?),
|
||||||
|
other_participants AS (
|
||||||
|
SELECT person_id, startDate, endDate
|
||||||
|
FROM chill_person_accompanying_period_participation
|
||||||
|
JOIN participations USING (accompanyingperiod_id)
|
||||||
|
WHERE person_id != ?
|
||||||
|
),
|
||||||
|
households AS (SELECT DISTINCT household.*
|
||||||
|
FROM chill_person_household_members AS hmembers
|
||||||
|
JOIN other_participants AS op USING (person_id)
|
||||||
|
JOIN chill_person_household AS household ON hmembers.household_id = household.id
|
||||||
|
WHERE daterange(op.startDate, op.endDate) && daterange(hmembers.startDate, hmembers.endDate)
|
||||||
|
)
|
||||||
|
SELECT {select} FROM households {limits}
|
||||||
|
SQL;
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,11 @@ final class PersonRepository
|
|||||||
return $this->repository->find($id, $lockMode, $lockVersion);
|
return $this->repository->find($id, $lockMode, $lockVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findByIds($ids): array
|
||||||
|
{
|
||||||
|
return $this->repository->findBy(['id' => $ids]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $centers
|
* @param $centers
|
||||||
* @param $firstResult
|
* @param $firstResult
|
||||||
|
@@ -23,6 +23,21 @@ const householdMove = (payload) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchHouseholdSuggestionByAccompanyingPeriod = (personId) => {
|
||||||
|
const url = `/api/1.0/person/household/suggest/by-person/${personId}/through-accompanying-period-participation.json`;
|
||||||
|
return window.fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Error ({m: 'Error while fetching household suggestion', status: response.status});
|
||||||
|
}).then(data => Promise.resolve(data.results))
|
||||||
|
.catch(e => console.err(e));
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
householdMove,
|
householdMove,
|
||||||
|
fetchHouseholdSuggestionByAccompanyingPeriod,
|
||||||
};
|
};
|
||||||
|
@@ -14,6 +14,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul v-if="allowChangeHousehold" class="record_actions">
|
<ul v-if="allowChangeHousehold" class="record_actions">
|
||||||
|
<li v-if="!showHouseholdSuggestion">
|
||||||
|
<button
|
||||||
|
class="sc-button"
|
||||||
|
@click="toggleHouseholdSuggestion"
|
||||||
|
>
|
||||||
|
{{ $tc('household_members_editor.show_household_suggestion',
|
||||||
|
countHouseholdSuggestion) }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li v-if="showHouseholdSuggestion">
|
||||||
|
<button
|
||||||
|
class="sc-button"
|
||||||
|
@click="toggleHouseholdSuggestion"
|
||||||
|
>
|
||||||
|
{{ $t('household_members_editor.hide_household_suggestion') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li v-if="allowHouseholdCreate">
|
<li v-if="allowHouseholdCreate">
|
||||||
<button class="sc-button bt-create" @click="createHousehold">
|
<button class="sc-button bt-create" @click="createHousehold">
|
||||||
{{ $t('household_members_editor.household.create_household') }}
|
{{ $t('household_members_editor.household.create_household') }}
|
||||||
@@ -30,12 +47,56 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="householdSuggestions">
|
||||||
|
<div v-if="showHouseholdSuggestion">
|
||||||
|
<p>{{ $t('household_members_editor.household_for_participants_accompanying_period') }}:</p>
|
||||||
|
<div class="householdSuggestionList">
|
||||||
|
<div
|
||||||
|
v-for="h in filterHouseholdSuggestionByAccompanyingPeriod"
|
||||||
|
class="item"
|
||||||
|
>
|
||||||
|
<household-viewer :household="h"></household-viewer>
|
||||||
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<button class="sc-button" @click="selectHousehold(h)">
|
||||||
|
{{ $t('household_members_editor.select_household') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div >
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
.householdSuggestionList {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
& > .item {
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
width: calc(50% - 1rem);
|
||||||
|
border: 1px solid var(--chill-light-gray);
|
||||||
|
padding: 0.5rem 0.5rem 0 0.5rem;
|
||||||
|
|
||||||
|
ul.record_actions {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters, mapState } from 'vuex';
|
||||||
import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue';
|
import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -47,6 +108,12 @@ export default {
|
|||||||
...mapGetters([
|
...mapGetters([
|
||||||
'hasHousehold',
|
'hasHousehold',
|
||||||
'isHouseholdNew',
|
'isHouseholdNew',
|
||||||
|
'hasHouseholdSuggestion',
|
||||||
|
'countHouseholdSuggestion',
|
||||||
|
'filterHouseholdSuggestionByAccompanyingPeriod',
|
||||||
|
]),
|
||||||
|
...mapState([
|
||||||
|
'showHouseholdSuggestion',
|
||||||
]),
|
]),
|
||||||
household() {
|
household() {
|
||||||
return this.$store.state.household;
|
return this.$store.state.household;
|
||||||
@@ -74,6 +141,12 @@ export default {
|
|||||||
},
|
},
|
||||||
forceLeaveWithoutHousehold() {
|
forceLeaveWithoutHousehold() {
|
||||||
this.$store.dispatch('forceLeaveWithoutHousehold');
|
this.$store.dispatch('forceLeaveWithoutHousehold');
|
||||||
|
},
|
||||||
|
toggleHouseholdSuggestion() {
|
||||||
|
this.$store.commit('toggleHouseholdSuggestion');
|
||||||
|
},
|
||||||
|
selectHousehold(h) {
|
||||||
|
this.$store.dispatch('selectHousehold', h);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -5,7 +5,7 @@ const appMessages = {
|
|||||||
fr: {
|
fr: {
|
||||||
household_members_editor: {
|
household_members_editor: {
|
||||||
household: {
|
household: {
|
||||||
no_household_choose_one: "Aucun ménage de destination. Choisissez un ménage.",
|
no_household_choose_one: "Aucun ménage de destination. Choisissez un ménage. Les usagers concernés par la modification apparaitront ensuite.",
|
||||||
new_household: "Nouveau ménage",
|
new_household: "Nouveau ménage",
|
||||||
create_household: "Créer un ménage",
|
create_household: "Créer un ménage",
|
||||||
search_household: "Chercher un ménage",
|
search_household: "Chercher un ménage",
|
||||||
@@ -13,7 +13,7 @@ const appMessages = {
|
|||||||
leave_without_household: "Sans nouveau ménage"
|
leave_without_household: "Sans nouveau ménage"
|
||||||
},
|
},
|
||||||
concerned: {
|
concerned: {
|
||||||
title: "Usagers concernés",
|
title: "Nouveaux membres du ménage",
|
||||||
add_persons: "Ajouter d'autres usagers",
|
add_persons: "Ajouter d'autres usagers",
|
||||||
search: "Rechercher des usagers",
|
search: "Rechercher des usagers",
|
||||||
move_to: "Déplacer vers",
|
move_to: "Déplacer vers",
|
||||||
@@ -29,6 +29,10 @@ const appMessages = {
|
|||||||
remove_position: "Retirer des {position}",
|
remove_position: "Retirer des {position}",
|
||||||
remove_concerned: "Ne plus transférer",
|
remove_concerned: "Ne plus transférer",
|
||||||
household_part: "Ménage de destination",
|
household_part: "Ménage de destination",
|
||||||
|
hide_household_suggestion: "Masquer les suggestions",
|
||||||
|
show_household_suggestion: 'Aucune suggestion | Afficher une suggestion | Afficher {count} suggestions',
|
||||||
|
household_for_participants_accompanying_period: "Ces ménages partagent le même parcours",
|
||||||
|
select_household: "Choisir ce ménage",
|
||||||
dates_title: "Période de validité",
|
dates_title: "Période de validité",
|
||||||
dates: {
|
dates: {
|
||||||
start_date: "Début de validité",
|
start_date: "Début de validité",
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { createStore } from 'vuex';
|
import { createStore } from 'vuex';
|
||||||
import { householdMove, householdMoveTest } from './../api.js';
|
import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod } from './../api.js';
|
||||||
import { datetimeToISO } from 'ChillMainAssets/js/date.js';
|
import { datetimeToISO } from 'ChillMainAssets/js/date.js';
|
||||||
|
|
||||||
const debug = process.env.NODE_ENV !== 'production';
|
const debug = process.env.NODE_ENV !== 'production';
|
||||||
@@ -14,6 +14,8 @@ const concerned = window.household_members_editor_data.persons.map(p => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('expand suggestions', window.household_members_editor_expand_suggestions === 1);
|
||||||
|
|
||||||
const store = createStore({
|
const store = createStore({
|
||||||
strict: debug,
|
strict: debug,
|
||||||
state: {
|
state: {
|
||||||
@@ -33,11 +35,16 @@ const store = createStore({
|
|||||||
allowHouseholdSearch: window.household_members_editor_data.allowHouseholdSearch,
|
allowHouseholdSearch: window.household_members_editor_data.allowHouseholdSearch,
|
||||||
allowLeaveWithoutHousehold: window.household_members_editor_data.allowLeaveWithoutHousehold,
|
allowLeaveWithoutHousehold: window.household_members_editor_data.allowLeaveWithoutHousehold,
|
||||||
forceLeaveWithoutHousehold: false,
|
forceLeaveWithoutHousehold: false,
|
||||||
|
householdSuggestionByAccompanyingPeriod: [],
|
||||||
|
showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1,
|
||||||
warnings: [],
|
warnings: [],
|
||||||
errors: []
|
errors: []
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
isHouseholdNew(state) {
|
isHouseholdNew(state) {
|
||||||
|
if (state.household === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return !Number.isInteger(state.household.id);
|
return !Number.isInteger(state.household.id);
|
||||||
},
|
},
|
||||||
hasHousehold(state) {
|
hasHousehold(state) {
|
||||||
@@ -46,6 +53,21 @@ const store = createStore({
|
|||||||
hasHouseholdOrLeave(state) {
|
hasHouseholdOrLeave(state) {
|
||||||
return state.household !== null || state.forceLeaveWithoutHousehold;
|
return state.household !== null || state.forceLeaveWithoutHousehold;
|
||||||
},
|
},
|
||||||
|
hasHouseholdSuggestion(state, getters) {
|
||||||
|
return getters.filterHouseholdSuggestionByAccompanyingPeriod.length > 0;
|
||||||
|
},
|
||||||
|
countHouseholdSuggestion(state, getters) {
|
||||||
|
return getters.filterHouseholdSuggestionByAccompanyingPeriod.length;
|
||||||
|
},
|
||||||
|
filterHouseholdSuggestionByAccompanyingPeriod(state) {
|
||||||
|
if (state.household === null) {
|
||||||
|
return state.householdSuggestionByAccompanyingPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.householdSuggestionByAccompanyingPeriod
|
||||||
|
.filter(h => h.id !== state.household.id)
|
||||||
|
;
|
||||||
|
},
|
||||||
hasPersonsWellPositionnated(state, getters) {
|
hasPersonsWellPositionnated(state, getters) {
|
||||||
return getters.needsPositionning === false
|
return getters.needsPositionning === false
|
||||||
|| (getters.persons.length > 0 && getters.concUnpositionned.length === 0);
|
|| (getters.persons.length > 0 && getters.concUnpositionned.length === 0);
|
||||||
@@ -172,9 +194,25 @@ const store = createStore({
|
|||||||
state.household = null;
|
state.household = null;
|
||||||
state.forceLeaveWithoutHousehold = true;
|
state.forceLeaveWithoutHousehold = true;
|
||||||
},
|
},
|
||||||
|
selectHousehold(state, household) {
|
||||||
|
state.household = household;
|
||||||
|
state.forceLeaveWithoutHousehold = false;
|
||||||
|
},
|
||||||
|
setHouseholdSuggestionByAccompanyingPeriod(state, households) {
|
||||||
|
let existingIds = state.householdSuggestionByAccompanyingPeriod
|
||||||
|
.map(h => h.id);
|
||||||
|
for (let i in households) {
|
||||||
|
if (!existingIds.includes(households[i].id)) {
|
||||||
|
state.householdSuggestionByAccompanyingPeriod.push(households[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
setStartDate(state, dateI) {
|
setStartDate(state, dateI) {
|
||||||
state.startDate = dateI;
|
state.startDate = dateI;
|
||||||
},
|
},
|
||||||
|
toggleHouseholdSuggestion(state) {
|
||||||
|
state.showHouseholdSuggestion = !state.showHouseholdSuggestion;
|
||||||
|
},
|
||||||
setWarnings(state, warnings) {
|
setWarnings(state, warnings) {
|
||||||
state.warnings = warnings;
|
state.warnings = warnings;
|
||||||
// reset errors, which should come from servers
|
// reset errors, which should come from servers
|
||||||
@@ -213,6 +251,10 @@ const store = createStore({
|
|||||||
commit('forceLeaveWithoutHousehold');
|
commit('forceLeaveWithoutHousehold');
|
||||||
dispatch('computeWarnings');
|
dispatch('computeWarnings');
|
||||||
},
|
},
|
||||||
|
selectHousehold({ commit }, h) {
|
||||||
|
commit('selectHousehold', h);
|
||||||
|
dispatch('computeWarnings');
|
||||||
|
},
|
||||||
setStartDate({ commit, dispatch }, date) {
|
setStartDate({ commit, dispatch }, date) {
|
||||||
commit('setStartDate', date);
|
commit('setStartDate', date);
|
||||||
dispatch('computeWarnings');
|
dispatch('computeWarnings');
|
||||||
@@ -220,6 +262,12 @@ const store = createStore({
|
|||||||
setComment({ commit }, payload) {
|
setComment({ commit }, payload) {
|
||||||
commit('setComment', payload);
|
commit('setComment', payload);
|
||||||
},
|
},
|
||||||
|
fetchHouseholdSuggestionForConcerned({ commit, state }, person) {
|
||||||
|
fetchHouseholdSuggestionByAccompanyingPeriod(person.id)
|
||||||
|
.then(households => {
|
||||||
|
commit('setHouseholdSuggestionByAccompanyingPeriod', households);
|
||||||
|
});
|
||||||
|
},
|
||||||
computeWarnings({ commit, state, getters }) {
|
computeWarnings({ commit, state, getters }) {
|
||||||
let warnings = [],
|
let warnings = [],
|
||||||
payload;
|
payload;
|
||||||
@@ -255,7 +303,7 @@ const store = createStore({
|
|||||||
if (household.type === 'household') {
|
if (household.type === 'household') {
|
||||||
household_id = household.id;
|
household_id = household.id;
|
||||||
// nothing to do anymore here, bye-bye !
|
// nothing to do anymore here, bye-bye !
|
||||||
window.location.replace(`/fr/person/household/${household_id}/members`);
|
window.location.replace(`/fr/person/household/${household_id}/summary`);
|
||||||
} else {
|
} else {
|
||||||
// we assume the answer was 422...
|
// we assume the answer was 422...
|
||||||
error = household;
|
error = household;
|
||||||
@@ -274,4 +322,10 @@ const store = createStore({
|
|||||||
|
|
||||||
store.dispatch('computeWarnings');
|
store.dispatch('computeWarnings');
|
||||||
|
|
||||||
|
if (concerned.length > 0) {
|
||||||
|
concerned.forEach(c => {
|
||||||
|
store.dispatch('fetchHouseholdSuggestionForConcerned', c.person);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export { store };
|
export { store };
|
||||||
|
@@ -39,12 +39,15 @@
|
|||||||
<ul>
|
<ul>
|
||||||
{% for p in withoutHousehold %}
|
{% for p in withoutHousehold %}
|
||||||
<li>
|
<li>
|
||||||
<input type="checkbox" name="persons[]" value="{{ p.id }}" />
|
<input type="checkbox" name="persons[]" value="{{ p.id }}" checked />
|
||||||
{{ p|chill_entity_render_box }}
|
{{ p|chill_entity_render_box }}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<input type="hidden" name="expand_suggestions" value="true" />
|
||||||
|
<input type="hidden" name="accompanying_period_id", value="{{ accompanyingCourse.id }}" />
|
||||||
|
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<button type="submit" class="sc-button bt-edit">
|
<button type="submit" class="sc-button bt-edit">
|
||||||
@@ -222,11 +225,107 @@
|
|||||||
|
|
||||||
<h2>{{ 'Social actions'|trans }}</h2>
|
<h2>{{ 'Social actions'|trans }}</h2>
|
||||||
|
|
||||||
{% set person = null %}
|
<div id="accompanying_course_work_list">
|
||||||
|
{% for w in works %}
|
||||||
|
<div class="item">
|
||||||
|
<div class="title">
|
||||||
|
<h2 class="action_title">
|
||||||
|
{{ w.socialAction|chill_entity_render_box({ 'no-badge': false }) }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="timeline">
|
||||||
|
<ul class="timeline">
|
||||||
|
<li class="completed">
|
||||||
|
<div class="date">
|
||||||
|
<span>{{ w.createdAt|format_date('long') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="label">
|
||||||
|
<span>{{ 'accompanying_course_work.create_date'|trans }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="completed">
|
||||||
|
<div class="date">
|
||||||
|
<span>{{ w.startDate|format_date('long') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="label">
|
||||||
|
<span>{{ 'accompanying_course_work.start_date'|trans }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="{%if date(w.endDate) < date('now') %}completed{% endif %}">
|
||||||
|
<div class="date">
|
||||||
|
<span>{{ w.endDate|format_date('long') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="label">
|
||||||
|
<span>{{ 'accompanying_course_work.end_date'|trans }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if w.results|length > 0 %}
|
||||||
|
<div class="objective_results objective_results__without-objectives">
|
||||||
|
<div class="obj without_objective">
|
||||||
|
<p class="title_label">{{ 'accompanying_course_work.goal'|trans }}</p>
|
||||||
|
<p class="chill-no-data-statement">{{ 'accompanying_course_work.results without objective'|trans }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="res results">
|
||||||
|
<p class="title_label">{{ 'accompanying_course_work.results'|trans }}</p>
|
||||||
|
<ul class="result_list">
|
||||||
|
{% for r in w.results %}
|
||||||
|
<li class="badge badge-primary">{{ r.title|localize_translatable_string }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if w.goals|length > 0 %}
|
||||||
|
{% for g in w.goals %}
|
||||||
|
<div class="objective_results objective_results--with-objectives">
|
||||||
|
<div class="objective obj">
|
||||||
|
<p class="title_label">{{ 'accompanying_course_work.goal'|trans }}</p>
|
||||||
|
<h3 class="goal_title">{{ g.goal.title|localize_translatable_string }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="results res">
|
||||||
|
{% if g.results|length == 0 %}
|
||||||
|
<p class="title_label">{{ 'accompanying_course_work.results'|trans }}</p>
|
||||||
|
<p class="chill-no-data-statement">{{ 'accompanying_course_work.no_results'|trans }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="title_label">{{ 'accompanying_course_work.results'|trans }}</p>
|
||||||
|
<ul class="result_list">
|
||||||
|
{% for r in g.results %}
|
||||||
|
<li class="badge badge-primary">{{ r.title|localize_translatable_string }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="updatedBy">
|
||||||
|
{{ 'Last updated by'|trans}}: {{ w.updatedBy|chill_entity_render_box }}, {{ w.updatedAt|format_datetime('long', 'short') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% block contentActivity %}
|
{% block contentActivity %}
|
||||||
|
{% set person = null %}
|
||||||
{% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'accompanyingCourse', 'context': 'person'} %}
|
{% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'accompanyingCourse', 'context': 'person'} %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{# ==> insert accompanyingCourse vue component #}
|
{# ==> insert accompanyingCourse vue component #}
|
||||||
<div id="accompanying-course"></div>
|
<div id="accompanying-course"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('accompanying_course_work_list') }}
|
||||||
|
{% endblock %}
|
||||||
|
@@ -102,7 +102,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="chill-no-data-statement">{{ 'accompanying_course_work.No work'|trans }}</p>
|
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -8,22 +8,28 @@
|
|||||||
|
|
||||||
<h1>{{ 'Accompanying period list'|trans }}</h1>
|
<h1>{{ 'Accompanying period list'|trans }}</h1>
|
||||||
|
|
||||||
<table class="rounded">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="chill-red">{{ 'accompanying_period.dates'|trans }}</th>
|
|
||||||
{% if chill_accompanying_periods.fields.user == 'visible' %}
|
|
||||||
<th class="chill-blue">{{ 'Accompanying user'|trans }}</th>
|
|
||||||
{% endif %}
|
|
||||||
<th class="chill-orange">{{ 'Remark'|trans }}</th>
|
|
||||||
<th> </th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</thead>
|
{% for accompanying_period in accompanying_periods %}
|
||||||
<tbody>
|
|
||||||
{% for accompanying_period in accompanying_periods %}
|
<div class="flex-table">
|
||||||
<tr>
|
<div class="item-bloc">
|
||||||
<td>
|
<div class="item-row">
|
||||||
|
<div class="item-col">
|
||||||
|
{{'Accompanying period'|trans}} #{{ accompanying_period.id }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-col">
|
||||||
|
{% if chill_accompanying_periods.fields.user == 'visible' %}
|
||||||
|
{% if accompanying_period.user %}
|
||||||
|
{{ accompanying_period.user.username }}
|
||||||
|
{% else %}
|
||||||
|
<span class="chill-no-data-statement">{{ 'No accompanying user'|trans }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="item-row">
|
||||||
{% if accompanying_period.closingDate == null %}
|
{% if accompanying_period.closingDate == null %}
|
||||||
{{ 'accompanying_period.dates_from_%opening_date%'|trans({ '%opening_date%': accompanying_period.openingDate|format_date('long') } ) }}
|
{{ 'accompanying_period.dates_from_%opening_date%'|trans({ '%opening_date%': accompanying_period.openingDate|format_date('long') } ) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -32,41 +38,51 @@
|
|||||||
'%closing_date%': accompanying_period.closingDate|format_date('long')}
|
'%closing_date%': accompanying_period.closingDate|format_date('long')}
|
||||||
) }}
|
) }}
|
||||||
|
|
||||||
{% if accompanying_period.isOpen == false %}
|
{% if accompanying_period.isOpen == false %}
|
||||||
<dl class="chill_view_data">
|
<dl class="chill_view_data">
|
||||||
<dt>{{ 'Closing motive'|trans }} :</dt>
|
<dt>{{ 'Closing motive'|trans }} :</dt>
|
||||||
<dd>{{ accompanying_period.closingMotive|chill_entity_render_box }}</dd>
|
<dd>{{ accompanying_period.closingMotive|chill_entity_render_box }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</div>
|
||||||
{% if chill_accompanying_periods.fields.user == 'visible' %}
|
<div class="item-row">
|
||||||
<td>
|
|
||||||
{% if accompanying_period.user %}
|
<h3>{{ 'Participants'|trans }}</h3>
|
||||||
{{ accompanying_period.user.username }}
|
{% if accompanying_period.participations.count > 0 %}
|
||||||
|
{% for p in accompanying_period.participations %}
|
||||||
|
<p>
|
||||||
|
<a href="{{ path('chill_person_accompanying_period_list', { person_id: p.person.id }) }}">
|
||||||
|
{{ p.person.firstname ~ ' ' ~ p.person.lastname }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<h3>{{ 'Requestors'|trans }}</h3>
|
||||||
|
{% if accompanying_period.requestorPerson is not null or accompanying_period.requestorThirdParty is not null %}
|
||||||
|
{% if accompanying_period.requestorPerson is not null %}
|
||||||
|
<p>{{ accompanying_period.requestorPerson.firstname ~ ' ' ~ accompanying_period.requestorPerson.lastname }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if accompanying_period.requestorThirdParty is not null %}
|
||||||
|
<p>{{ accompanying_period.requestorThirdParty.name }}</p>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="chill-no-data-statement">{{ 'No accompanying user'|trans }}</span>
|
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<td>
|
<div class="item-row">
|
||||||
{% if accompanying_period is not empty %}
|
|
||||||
<blockquote class="chill-user-quote">
|
|
||||||
{{ accompanying_period.remark|chill_markdown_to_html }}
|
|
||||||
</blockquote>
|
|
||||||
{% else %}
|
|
||||||
{{ null|chill_print_or_message('No remark', 'blockquote') }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
|
|
||||||
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
|
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': accompanying_period.id }) }}" class="sc-button bt-show"></a>
|
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': accompanying_period.id }) }}" class="sc-button bt-show"></a>
|
||||||
</li>
|
</li>
|
||||||
|
{#
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_person_accompanying_period_update', {'person_id' : person.id, 'period_id' : accompanying_period.id } ) }}" class="sc-button bt-update no-content"></a>
|
<a href="{{ path('chill_person_accompanying_period_update', {'person_id' : person.id, 'period_id' : accompanying_period.id } ) }}" class="sc-button bt-update no-content"></a>
|
||||||
</li>
|
</li>
|
||||||
@@ -84,21 +100,29 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
#}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
{% endfor %}
|
<p></p>
|
||||||
</tbody>
|
{% endfor %}
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="form_control">
|
|
||||||
|
<div class="form_control">
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
<a href="{{ path ('chill_person_view', {'person_id' : person.id } ) }}" class="sc-button bt-cancel">
|
<a href="{{ path ('chill_person_view', {'person_id' : person.id } ) }}" class="sc-button bt-cancel">
|
||||||
{{ 'Person details'|trans }}
|
{{ 'Person details'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ path ('chill_person_accompanying_course_new', {'person_id' : [ person.id ] } ) }}" class="sc-button bt-create">
|
||||||
|
{{ 'Create an accompanying period'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{#
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path ('chill_person_accompanying_period_create', {'person_id' : person.id } ) }}" class="sc-button bt-create has-hidden">
|
<a href="{{ path ('chill_person_accompanying_period_create', {'person_id' : person.id } ) }}" class="sc-button bt-create has-hidden">
|
||||||
<span class="show-on-hover">{{ 'Add an accompanying period in the past'|trans }}</span>
|
<span class="show-on-hover">{{ 'Add an accompanying period in the past'|trans }}</span>
|
||||||
@@ -112,6 +136,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
#}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -30,19 +30,19 @@
|
|||||||
<div id="banner-misc">
|
<div id="banner-misc">
|
||||||
{%- set members = household.getCurrentMembersOrdered() -%}
|
{%- set members = household.getCurrentMembersOrdered() -%}
|
||||||
{%- if members|length > 0 -%}
|
{%- if members|length > 0 -%}
|
||||||
<span class="current-members-explain">
|
<span class="current-members-explain">
|
||||||
{{- 'household.Current household members'|trans }}:
|
{{- 'household.Current household members'|trans }}:
|
||||||
</span>
|
</span>
|
||||||
{%- for m in members|slice(0, 5) -%}
|
{%- for m in members|slice(0, 5) -%}
|
||||||
{{- m.person|chill_entity_render_box({'addLink': false}) -}}
|
{{- m.person|chill_entity_render_box({'addLink': false}) -}}
|
||||||
{%- if m.holder %} <span class="badge badge-primary">{{ 'household.holder'|trans }}</span>{% endif -%}
|
{%- if m.holder %} <span class="badge badge-primary">{{ 'household.holder'|trans }}</span>{% endif -%}
|
||||||
{%- if false == loop.last -%}, {% endif -%}
|
{%- if false == loop.last -%}, {% endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{% if members|length > 5 %}
|
{% if members|length > 5 %}
|
||||||
<span class="current-members-more">
|
<span class="current-members-more">
|
||||||
{{ 'household.and x other persons'|trans({'x': members|length-5}) }}
|
{{ 'household.and x other persons'|trans({'x': members|length-5}) }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
{% extends '@ChillMain/layout.html.twig' %}
|
{% extends accompanyingCourse != null ? '@ChillPerson/AccompanyingCourse/layout.html.twig'
|
||||||
|
: '@ChillMain/layout.html.twig' %}
|
||||||
|
|
||||||
{% block title 'household.Edit household members'|trans %}
|
{% block title 'household.Edit household members'|trans %}
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
{% block js %}
|
{% block js %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.household_members_editor_data = {{ data|json_encode|raw }};
|
window.household_members_editor_data = {{ data|json_encode|raw }};
|
||||||
|
window.household_members_editor_expand_suggestions = {{ expandSuggestions }};
|
||||||
</script>
|
</script>
|
||||||
{{ encore_entry_script_tags('household_members_editor') }}
|
{{ encore_entry_script_tags('household_members_editor') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Search;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Repository\PersonRepository;
|
||||||
|
use Chill\MainBundle\Search\SearchApiQuery;
|
||||||
|
use Chill\MainBundle\Search\SearchApiInterface;
|
||||||
|
|
||||||
|
class SearchPersonApiProvider implements SearchApiInterface
|
||||||
|
{
|
||||||
|
private PersonRepository $personRepository;
|
||||||
|
|
||||||
|
public function __construct(PersonRepository $personRepository)
|
||||||
|
{
|
||||||
|
$this->personRepository = $personRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideQuery(string $pattern, array $parameters): SearchApiQuery
|
||||||
|
{
|
||||||
|
$query = new SearchApiQuery();
|
||||||
|
$query
|
||||||
|
->setSelectKey("person")
|
||||||
|
->setSelectJsonbMetadata("jsonb_build_object('id', person.id)")
|
||||||
|
->setSelectPertinence("SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical)", [ $pattern ])
|
||||||
|
->setFromClause("chill_person_person AS person")
|
||||||
|
->setWhereClause("SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical) > 0.20", [ $pattern ])
|
||||||
|
;
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsTypes(string $pattern, array $types, array $parameters): bool
|
||||||
|
{
|
||||||
|
return \in_array('person', $types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepare(array $metadatas): void
|
||||||
|
{
|
||||||
|
$ids = \array_map(fn($m) => $m['id'], $metadatas);
|
||||||
|
|
||||||
|
$this->personRepository->findByIds($ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsResult(string $key, array $metadatas): bool
|
||||||
|
{
|
||||||
|
return $key === 'person';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult(string $key, array $metadata, float $pertinence)
|
||||||
|
{
|
||||||
|
return $this->personRepository->find($metadata['id']);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Tests\Controller;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
|
|
||||||
|
class HouseholdApiControllerTest extends WebTestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
use PrepareClientTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider generatePersonId
|
||||||
|
*/
|
||||||
|
public function testSuggestByAccompanyingPeriodParticipation(int $personId)
|
||||||
|
{
|
||||||
|
$client = $this->getClientAuthenticated();
|
||||||
|
|
||||||
|
$client->request(
|
||||||
|
Request::METHOD_GET,
|
||||||
|
"/api/1.0/person/household/suggest/by-person/{$personId}/through-accompanying-period-participation.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generatePersonId()
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
|
||||||
|
$qb = self::$container->get(EntityManagerInterface::class)
|
||||||
|
->createQueryBuilder();
|
||||||
|
|
||||||
|
$period = $qb
|
||||||
|
->select('ap')
|
||||||
|
->from(AccompanyingPeriod::class, 'ap')
|
||||||
|
->where(
|
||||||
|
$qb->expr()->gte('SIZE(ap.participations)', 2)
|
||||||
|
)
|
||||||
|
->getQuery()
|
||||||
|
->setMaxResults(1)
|
||||||
|
->getSingleResult()
|
||||||
|
;
|
||||||
|
|
||||||
|
$person = $period->getParticipations()
|
||||||
|
->first()->getPerson();
|
||||||
|
|
||||||
|
yield [ $person->getId() ];
|
||||||
|
}
|
||||||
|
}
|
@@ -967,6 +967,36 @@ paths:
|
|||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
/1.0/person/household/suggest/by-person/{person_id}/through-accompanying-period-participation.json:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- household
|
||||||
|
summary: Return households associated with the given person through accompanying periods
|
||||||
|
description: |
|
||||||
|
Return households associated with the given person throught accompanying periods participation.
|
||||||
|
|
||||||
|
The current household of the given person is excluded.
|
||||||
|
parameters:
|
||||||
|
- name: person_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The person's id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Household'
|
||||||
|
404:
|
||||||
|
description: "not found"
|
||||||
|
401:
|
||||||
|
description: "Unauthorized"
|
||||||
|
|
||||||
/1.0/person/household/members/move.json:
|
/1.0/person/household/members/move.json:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
@@ -14,6 +14,6 @@ module.exports = function(encore, entries)
|
|||||||
encore.addEntry('household_edit_metadata', __dirname + '/Resources/public/modules/household_edit_metadata/index.js');
|
encore.addEntry('household_edit_metadata', __dirname + '/Resources/public/modules/household_edit_metadata/index.js');
|
||||||
encore.addEntry('accompanying_course_work_create', __dirname + '/Resources/public/vuejs/AccompanyingCourseWorkCreate/index.js');
|
encore.addEntry('accompanying_course_work_create', __dirname + '/Resources/public/vuejs/AccompanyingCourseWorkCreate/index.js');
|
||||||
encore.addEntry('accompanying_course_work_edit', __dirname + '/Resources/public/vuejs/AccompanyingCourseWorkEdit/index.js');
|
encore.addEntry('accompanying_course_work_edit', __dirname + '/Resources/public/vuejs/AccompanyingCourseWorkEdit/index.js');
|
||||||
encore.addEntry('accompanying_course_work_list', __dirname + '/Resources/public/modules/accompanying_course_work_list/index.js');
|
|
||||||
encore.addEntry('person', __dirname + '/Resources/public/js/person.js');
|
encore.addEntry('person', __dirname + '/Resources/public/js/person.js');
|
||||||
|
encore.addEntry('accompanying_course_work_list', __dirname + '/Resources/public/modules/accompanying_course_work_list/index.js');
|
||||||
};
|
};
|
||||||
|
@@ -46,6 +46,7 @@ services:
|
|||||||
$serializer: '@Symfony\Component\Serializer\SerializerInterface'
|
$serializer: '@Symfony\Component\Serializer\SerializerInterface'
|
||||||
$dispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface'
|
$dispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface'
|
||||||
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
|
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
|
||||||
|
$workRepository: '@Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository'
|
||||||
tags: ['controller.service_arguments']
|
tags: ['controller.service_arguments']
|
||||||
|
|
||||||
Chill\PersonBundle\Controller\AccompanyingCourseApiController:
|
Chill\PersonBundle\Controller\AccompanyingCourseApiController:
|
||||||
|
@@ -28,3 +28,7 @@ services:
|
|||||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||||
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
||||||
|
|
||||||
|
Chill\PersonBundle\Search\SearchPersonApiProvider:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
@@ -165,6 +165,7 @@ An accompanying period starts: Une période d'accompagnement est ouverte
|
|||||||
Any accompanying periods are open: Aucune période d'accompagnement ouverte
|
Any accompanying periods are open: Aucune période d'accompagnement ouverte
|
||||||
An accompanying period is open: Une période d'accompagnement est ouverte
|
An accompanying period is open: Une période d'accompagnement est ouverte
|
||||||
Accompanying period list: Périodes d'accompagnement
|
Accompanying period list: Périodes d'accompagnement
|
||||||
|
Accompanying period: Période d'accompagnement
|
||||||
New accompanying course: Nouveau parcours d'accompagnement
|
New accompanying course: Nouveau parcours d'accompagnement
|
||||||
Choose a motive: Motif de fermeture
|
Choose a motive: Motif de fermeture
|
||||||
Re-open accompanying period: Ré-ouvrir
|
Re-open accompanying period: Ré-ouvrir
|
||||||
@@ -353,4 +354,5 @@ accompanying_course_work:
|
|||||||
no_results: Aucun résultat - orientation
|
no_results: Aucun résultat - orientation
|
||||||
results: Résultats - orientations
|
results: Résultats - orientations
|
||||||
goal: Objectif - motif - dispositif
|
goal: Objectif - motif - dispositif
|
||||||
|
Any work: Aucune action d'accompagnement
|
||||||
|
|
||||||
|
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\ThirdPartyBundle\Search;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Search\SearchApiInterface;
|
||||||
|
use Chill\MainBundle\Search\SearchApiQuery;
|
||||||
|
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
|
||||||
|
|
||||||
|
class ThirdPartyApiSearch implements SearchApiInterface
|
||||||
|
{
|
||||||
|
private ThirdPartyRepository $thirdPartyRepository;
|
||||||
|
|
||||||
|
public function __construct(ThirdPartyRepository $thirdPartyRepository)
|
||||||
|
{
|
||||||
|
$this->thirdPartyRepository = $thirdPartyRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideQuery(string $pattern, array $parameters): SearchApiQuery
|
||||||
|
{
|
||||||
|
return (new SearchApiQuery)
|
||||||
|
->setSelectKey('tparty')
|
||||||
|
->setSelectJsonbMetadata("jsonb_build_object('id', tparty.id)")
|
||||||
|
->setSelectPertinence("SIMILARITY(?, LOWER(UNACCENT(tparty.name)))", [ $pattern ])
|
||||||
|
->setFromClause('chill_3party.third_party AS tparty')
|
||||||
|
->setWhereClause('SIMILARITY(LOWER(UNACCENT(?)), LOWER(UNACCENT(tparty.name))) > 0.20', [ $pattern ])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsTypes(string $pattern, array $types, array $parameters): bool
|
||||||
|
{
|
||||||
|
return \in_array('thirdparty', $types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepare(array $metadatas): void
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsResult(string $key, array $metadatas): bool
|
||||||
|
{
|
||||||
|
return $key === 'tparty';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult(string $key, array $metadata, float $pertinence)
|
||||||
|
{
|
||||||
|
return $this->thirdPartyRepository->find($metadata['id']);
|
||||||
|
}
|
||||||
|
}
|
@@ -7,3 +7,7 @@ services:
|
|||||||
$paginatorFactory: '@Chill\MainBundle\Pagination\PaginatorFactory'
|
$paginatorFactory: '@Chill\MainBundle\Pagination\PaginatorFactory'
|
||||||
tags:
|
tags:
|
||||||
- { name: 'chill.search', alias: '3party' }
|
- { name: 'chill.search', alias: '3party' }
|
||||||
|
|
||||||
|
Chill\ThirdPartyBundle\Search\ThirdPartyApiSearch:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
Reference in New Issue
Block a user