mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-26 00:24:59 +00:00
Compare commits
54 Commits
deploy/qui
...
deploy/qui
Author | SHA1 | Date | |
---|---|---|---|
96d302ccf7 | |||
|
0b5daa6c33 | ||
662e5f967c | |||
802125edc6 | |||
028c3e0c31 | |||
e4adafafc8 | |||
54316d148c | |||
3c5a2a65df | |||
741ef7b40d | |||
e7384343b2 | |||
1e2a920200 | |||
b5a78fa341 | |||
d9c1f52894 | |||
|
f05f7415bd | ||
|
5553c7314c | ||
7dc70baf57 | |||
|
34af401a98 | ||
5bae8dc5bf | |||
6e277e99ae | |||
2688c0f0ce | |||
|
0135f5184e | ||
fd698419e8 | |||
4a864a0b14 | |||
55dc0d3f9f | |||
85835c8b0d | |||
|
ab1edee9fb | ||
6154e0912b | |||
0388428c6f | |||
7ac55275c2 | |||
1119e20a49 | |||
ea2870eef8 | |||
|
93a44d8303 | ||
|
8109415e05 | ||
|
514fe6163a | ||
|
4cf3cc8353 | ||
dd991e3572 | |||
0640631821 | |||
e845d9ba90 | |||
|
6f8231f6f6 | ||
|
048161e300 | ||
|
4f49292178 | ||
6eaffcae49 | |||
|
f92d710a26 | ||
|
bc6ba88acd | ||
|
b72d45d9db | ||
2a1f5cbad1 | |||
e8566fd006 | |||
cd6b5c9a39 | |||
9aa3974071 | |||
a8bf478ee8 | |||
a35f3363b2 | |||
a43e6c12a0 | |||
abe012de60 | |||
aaa9a1ec7b |
@@ -72,6 +72,7 @@ class ActivityController extends AbstractController
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$view = null;
|
||||
// TODO: add pagination
|
||||
|
||||
[$person, $accompanyingPeriod] = $this->getEntity($request);
|
||||
|
||||
@@ -80,10 +81,9 @@ class ActivityController extends AbstractController
|
||||
->getReachableCircles($this->getUser(), new Role('CHILL_ACTIVITY_SEE'),
|
||||
$person->getCenter());
|
||||
|
||||
$activities = $em->getRepository('ChillActivityBundle:Activity')->findBy(
|
||||
['person' => $person, 'scope' => $reachableScopes],
|
||||
['date' => 'DESC'],
|
||||
);
|
||||
$activities = $em->getRepository(Activity::class)
|
||||
->findByPersonImplied($person, $reachableScopes)
|
||||
;
|
||||
|
||||
$event = new PrivacyEvent($person, array(
|
||||
'element_class' => Activity::class,
|
||||
|
@@ -224,7 +224,8 @@ class ActivityType extends AbstractType
|
||||
|
||||
if ($activityType->isVisible('comment')) {
|
||||
$builder->add('comment', CommentType::class, [
|
||||
'label' => $activityType->getLabel('comment'),
|
||||
'label' => empty($activityType->getLabel('comment'))
|
||||
? 'activity.comment' : $activityType->getLabel('comment'),
|
||||
'required' => $activityType->isRequired('comment'),
|
||||
]);
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ namespace Chill\ActivityBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
@@ -34,21 +35,13 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
$period = $parameters['accompanyingCourse'];
|
||||
|
||||
$menu->addChild($this->translator->trans('Activity list'), [
|
||||
'route' => 'chill_activity_activity_list',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
]])
|
||||
->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]);
|
||||
|
||||
|
||||
|
||||
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) {
|
||||
$menu->addChild($this->translator->trans('Activity list'), [
|
||||
'route' => 'chill_activity_activity_list',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
]])
|
||||
->setExtras(['order' => 40]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
*
|
||||
*/
|
||||
namespace Chill\ActivityBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class MenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
protected $tokenStorage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
protected $authorizationHelper;
|
||||
|
||||
public function __construct(
|
||||
TokenStorageInterface $tokenStorage,
|
||||
TranslatorInterface $translator,
|
||||
AuthorizationHelper $authorizationHelper
|
||||
) {
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->translator = $translator;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
}
|
||||
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
/* @var $person \Chill\PersonBundle\Entity\Person */
|
||||
$person = $parameters['person'];
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
$roleSee = new Role(ActivityVoter::SEE);
|
||||
$roleAdd = new Role(ActivityVoter::CREATE);
|
||||
|
||||
if ($this->authorizationHelper->userHasAccess($user, $person, $roleSee)) {
|
||||
$menu->addChild($this->translator->trans('Activity list'), [
|
||||
'route' => 'chill_activity_activity_list',
|
||||
'routeParameters' => [
|
||||
'person_id' => $person->getId()
|
||||
]
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 201
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->authorizationHelper->userHasAccess($user, $person, $roleAdd)) {
|
||||
$menu->addChild($this->translator->trans('Add a new activity'), [
|
||||
'route' => 'chill_activity_activity_new',
|
||||
'routeParameters' => [
|
||||
'person_id' => $person->getId()
|
||||
]
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 200
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return [ 'person' ];
|
||||
}
|
||||
}
|
@@ -15,8 +15,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\ActivityBundle\Menu;
|
||||
|
||||
namespace Chill\ActivityBundle\Menu;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
@@ -65,15 +64,6 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
->setExtra('order', 201)
|
||||
;
|
||||
}
|
||||
if ($this->authorizationChecker->isGranted(ActivityVoter::CREATE, $person)) {
|
||||
$menu->addChild(
|
||||
$this->translator->trans('Add a new activity'), [
|
||||
'route' => 'chill_activity_activity_new',
|
||||
'routeParameters' => [ 'person_id' => $person->getId() ],
|
||||
])
|
||||
->setExtra('order', 200)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
|
@@ -38,5 +38,30 @@ class ActivityRepository extends ServiceEntityRepository
|
||||
{
|
||||
parent::__construct($registry, Activity::class);
|
||||
}
|
||||
|
||||
|
||||
public function findByPersonImplied($person, array $scopes, $orderBy = [ 'date' => 'DESC'], $limit = 100, $offset = 0)
|
||||
{
|
||||
$qb = $this->createQueryBuilder('a');
|
||||
$qb->select('a');
|
||||
|
||||
$qb
|
||||
// TODO add acl
|
||||
//->where($qb->expr()->in('a.scope', ':scopes'))
|
||||
//->setParameter('scopes', $scopes)
|
||||
->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('a.person', ':person'),
|
||||
':person MEMBER OF a.persons'
|
||||
)
|
||||
)
|
||||
->setParameter('person', $person)
|
||||
;
|
||||
|
||||
foreach ($orderBy as $k => $dir) {
|
||||
$qb->addOrderBy('a.'.$k, $dir);
|
||||
}
|
||||
|
||||
return $qb->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
@@ -99,7 +99,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="sc-button bt-update" type="submit">{{ 'Save activity'|trans }}</button>
|
||||
<button class="sc-button bt-update" type="submit">{{ 'Save'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(edit_form) }}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
@@ -22,6 +23,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('async_upload') }}
|
||||
{{ encore_entry_link_tags('vue_activity') }}
|
||||
{% endblock %}
|
||||
|
@@ -17,7 +17,7 @@
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
<div class="flex-table list-records {{ context }}">
|
||||
<div class="flex-table list-records context-{{ context }}">
|
||||
<!--
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -54,6 +54,20 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
||||
<div class="accompanyingPeriodLink" style="margin-top: 1rem">
|
||||
<a
|
||||
href="{{ chill_path_add_return_path(
|
||||
"chill_person_accompanying_course_index",
|
||||
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
|
||||
) }}"
|
||||
>
|
||||
<i class="fa fa-random"></i>
|
||||
{{ activity.accompanyingPeriod.id }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="item-col">
|
||||
@@ -147,13 +161,14 @@
|
||||
</div>
|
||||
|
||||
{%
|
||||
if activity.comment.comment is not empty
|
||||
if activity.comment.comment is not empty
|
||||
or activity.persons|length > 0
|
||||
or activity.thirdParties|length > 0
|
||||
or activity.users|length > 0
|
||||
%}
|
||||
<div class="item-row details">
|
||||
<div class="item-col">
|
||||
|
||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': context, 'with_display': 'row', 'entity': activity } %}
|
||||
</div>
|
||||
|
||||
@@ -170,10 +185,13 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="sc-button bt-create">
|
||||
{{ 'Add a new activity' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% if context != 'person' %}
|
||||
{# TODO set this condition in configuration #}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="sc-button bt-create">
|
||||
{{ 'Add a new activity' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@@ -98,7 +98,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<button class="sc-button bt-create" type="submit">
|
||||
{{ 'Add a new activity'|trans }}
|
||||
{{ 'Create'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
@@ -10,6 +10,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
@@ -22,6 +23,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
<link rel="stylesheet" href="{{ asset('build/async_upload.css') }}"/>
|
||||
{{ encore_entry_link_tags('vue_activity') }}
|
||||
{% endblock %}
|
||||
|
@@ -117,7 +117,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a class="sc-button bt-update" href="{{ path('chill_activity_activity_edit', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}">
|
||||
{{ 'Edit the activity'|trans }}
|
||||
{{ 'Edit'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{# TODO
|
||||
|
@@ -74,6 +74,7 @@ Users concerned: T(M)S
|
||||
activity:
|
||||
Insert a document: Insérer un document
|
||||
Remove a document: Supprimer le document
|
||||
comment: Commentaire
|
||||
|
||||
|
||||
#timeline
|
||||
|
@@ -36,6 +36,7 @@ use Chill\MainBundle\Search\SearchProvider;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Search\SearchApi;
|
||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||
|
||||
/**
|
||||
* Class SearchController
|
||||
@@ -151,11 +152,14 @@ class SearchController extends AbstractController
|
||||
{
|
||||
//TODO this is an incomplete implementation
|
||||
$query = $request->query->get('q', '');
|
||||
$types = $request->query->get('type', []);
|
||||
|
||||
$results = $this->searchApi->getResults($query, 0, 150);
|
||||
$paginator = $this->paginatorFactory->create(count($results));
|
||||
if (count($types) === 0) {
|
||||
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);
|
||||
}
|
||||
|
27
src/Bundle/ChillMainBundle/Controller/UserApiController.php
Normal file
27
src/Bundle/ChillMainBundle/Controller/UserApiController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class UserApiController extends ApiController
|
||||
{
|
||||
/**
|
||||
* @Route(
|
||||
* "/api/1.0/main/whoami.{_format}",
|
||||
* name="chill_main_user_whoami",
|
||||
* requirements={
|
||||
* "_format": "json"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function whoami($_format): JsonResponse
|
||||
{
|
||||
return $this->json($this->getUser(), JsonResponse::HTTP_OK, [],
|
||||
[ "groups" => [ "read" ] ]);
|
||||
}
|
||||
|
||||
}
|
@@ -347,7 +347,28 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'class' => \Chill\MainBundle\Entity\User::class,
|
||||
'controller' => \Chill\MainBundle\Controller\UserApiController::class,
|
||||
'name' => 'user',
|
||||
'base_path' => '/api/1.0/main/user',
|
||||
'base_role' => 'ROLE_USER',
|
||||
'actions' => [
|
||||
'_index' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true
|
||||
],
|
||||
],
|
||||
'_entity' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
]
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@@ -132,6 +132,7 @@ class Address
|
||||
/**
|
||||
* True if the address is a "no address", aka homeless person, ...
|
||||
* @groups({"write"})
|
||||
* @ORM\Column(type="boolean")
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
@@ -298,7 +299,7 @@ class Address
|
||||
* @param bool $isNoAddress
|
||||
* @return $this
|
||||
*/
|
||||
public function setIsNoAddress(bool $isNoAddress)
|
||||
public function setIsNoAddress(bool $isNoAddress): self
|
||||
{
|
||||
$this->isNoAddress = $isNoAddress;
|
||||
return $this;
|
||||
|
@@ -45,7 +45,8 @@ class CommentType extends AbstractType
|
||||
{
|
||||
$builder
|
||||
->add('comment', ChillTextareaType::class, [
|
||||
'disable_editor' => $options['disable_editor']
|
||||
'disable_editor' => $options['disable_editor'],
|
||||
'label' => $options['label'],
|
||||
])
|
||||
;
|
||||
|
||||
|
@@ -35,6 +35,7 @@ $table-body-td-text-align: left;
|
||||
// Tabs
|
||||
$tabs-nav-margin-bottom: 0.2em;
|
||||
$tabs-nav-bg-color: $yellow;
|
||||
$tabs-nav-bg-color-light: lighten($yellow, 10%);
|
||||
$tabs-nav-text-color: $blue;
|
||||
$tabs-new-border: none;
|
||||
$tabs-nav-hover-border: none;
|
||||
|
@@ -26,7 +26,13 @@ $global-bg-color: $yellow;
|
||||
color: $tabs-nav-title-text-color;
|
||||
}
|
||||
|
||||
> a {
|
||||
&.sub-menu {
|
||||
padding-left: 20px;
|
||||
> a {
|
||||
background-color: $tabs-nav-bg-color-light;
|
||||
}
|
||||
}
|
||||
> a {
|
||||
display: block;
|
||||
width: auto;
|
||||
padding: $tabs-nav-padding;
|
||||
@@ -38,11 +44,11 @@ $global-bg-color: $yellow;
|
||||
@include border-top-radius($base-border-radius);
|
||||
@include border-bottom-radius($base-border-radius);
|
||||
|
||||
&:hover, &:active {
|
||||
&:hover, &:active {
|
||||
border: $tabs-nav-hover-border;
|
||||
color: $tabs-nav-hover-text-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@
|
||||
|
||||
<city-selection
|
||||
v-bind:address="address"
|
||||
v-bind:focusOnAddress="focusOnAddress"
|
||||
v-bind:getReferenceAddresses="getReferenceAddresses">
|
||||
</city-selection>
|
||||
|
||||
@@ -173,6 +174,14 @@ export default {
|
||||
// this.$refs.search.focus(); // positionner le curseur à l'ouverture de la modale
|
||||
//})
|
||||
},
|
||||
focusOnCity() {
|
||||
const citySelector = document.getElementById('citySelector');
|
||||
citySelector.focus();
|
||||
},
|
||||
focusOnAddress() {
|
||||
const addressSelector = document.getElementById('addressSelector');
|
||||
addressSelector.focus();
|
||||
},
|
||||
getCountries() {
|
||||
console.log('getCountries');
|
||||
this.loading = true;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<VueMultiselect
|
||||
id="addressSelector"
|
||||
v-model="value"
|
||||
@select="selectAddress"
|
||||
name="field"
|
||||
@@ -70,7 +71,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
transName(value) {
|
||||
return value.streetNumber === undefined ? value.street : `${value.street}, ${value.streetNumber}`
|
||||
return value.streetNumber === undefined ? value.street : `${value.streetNumber}, ${value.street}`
|
||||
},
|
||||
selectAddress(value) {
|
||||
this.address.selected.address = value;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<VueMultiselect
|
||||
id="citySelector"
|
||||
v-model="value"
|
||||
@select="selectCity"
|
||||
name="field"
|
||||
@@ -35,7 +36,7 @@ import VueMultiselect from 'vue-multiselect';
|
||||
export default {
|
||||
name: 'CitySelection',
|
||||
components: { VueMultiselect },
|
||||
props: ['address', 'getReferenceAddresses'],
|
||||
props: ['address', 'getReferenceAddresses', 'focusOnAddress'],
|
||||
data() {
|
||||
return {
|
||||
value: null
|
||||
@@ -74,6 +75,7 @@ export default {
|
||||
this.address.newPostalCode.name = value.name;
|
||||
this.address.newPostalCode.code = value.code;
|
||||
this.getReferenceAddresses(value);
|
||||
this.focusOnAddress();
|
||||
},
|
||||
addPostalCode() {
|
||||
this.address.writeNewPostalCode = true;
|
||||
|
@@ -40,7 +40,6 @@ export default {
|
||||
return name.fr //TODO multilang
|
||||
},
|
||||
selectCountry(value) {
|
||||
console.log(value);
|
||||
this.address.selected.country = value;
|
||||
this.getCities(value);
|
||||
},
|
||||
|
@@ -2,88 +2,169 @@
|
||||
|
||||
namespace Chill\MainBundle\Search;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
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\Query\ResultSetMappingBuilder;
|
||||
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;
|
||||
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->search = $search;
|
||||
$this->providers[] = $searchPerson;
|
||||
$this->providers[] = $thirdPartyApiSearch;
|
||||
$this->paginator = $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
$results = [];
|
||||
$queries = $this->findQueries($pattern, $types, $parameters);
|
||||
|
||||
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);
|
||||
}
|
||||
$total = $this->countItems($queries, $types, $parameters);
|
||||
$paginator = $this->paginator->create($total);
|
||||
|
||||
\usort($results, function(Model\Result $a, Model\Result $b) {
|
||||
return ($a->getRelevance() <=> $b->getRelevance()) * -1;
|
||||
});
|
||||
$rawResults = $this->fetchRawResult($queries, $types, $parameters, $paginator);
|
||||
|
||||
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')
|
||||
->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;
|
||||
return \array_filter(
|
||||
$this->providers,
|
||||
fn($p) => $p->supportsTypes($pattern, $types, $parameters)
|
||||
);
|
||||
}
|
||||
|
||||
private function getPersons(string $query)
|
||||
private function countItems($providers, $types, $parameters): int
|
||||
{
|
||||
$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']);
|
||||
list($countQuery, $parameters) = $this->buildCountQuery($providers, $types, $parameters);
|
||||
$rsmCount = new ResultSetMappingBuilder($this->em);
|
||||
$rsmCount->addScalarResult('count', 'count');
|
||||
$countNq = $this->em->createNativeQuery($countQuery, $rsmCount);
|
||||
$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) {
|
||||
return [];
|
||||
foreach ($queries as $q) {
|
||||
$unions[] = $q->buildQuery();
|
||||
$parameters = \array_merge($parameters, $q->buildParameters());
|
||||
}
|
||||
|
||||
return $this->em->getRepository(Person::class)
|
||||
->findById($ids)
|
||||
$unionUnordered = \implode(" UNION ", $unions);
|
||||
|
||||
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 = [])
|
||||
{
|
||||
$data['address_id'] = $address->getId();
|
||||
$data['text'] = $address->getStreet().', '.$address->getStreetNumber();
|
||||
$data['text'] = $address->isNoAddress() ? '' : $address->getStreetNumber().', '.$address->getStreet();
|
||||
$data['street'] = $address->getStreet();
|
||||
$data['streetNumber'] = $address->getStreetNumber();
|
||||
$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'] ];
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Tests\Controller;
|
||||
|
||||
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class UserApiControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
public function testIndex()
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request(Request::METHOD_GET, '/api/1.0/main/user.json');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$data = \json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertTrue(\array_key_exists('count', $data));
|
||||
$this->assertGreaterThan(0, $data['count']);
|
||||
$this->assertTrue(\array_key_exists('results', $data));
|
||||
|
||||
return $data['results'][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testIndex
|
||||
*/
|
||||
public function testEntity($existingUser)
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request(Request::METHOD_GET, '/api/1.0/main/user/'.$existingUser['id'].'.json');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public function testWhoami()
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request(Request::METHOD_GET, '/api/1.0/main/whoami.json');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
}
|
@@ -10,6 +10,19 @@ servers:
|
||||
|
||||
components:
|
||||
schemas:
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- user
|
||||
username:
|
||||
type: string
|
||||
text:
|
||||
type: string
|
||||
Center:
|
||||
type: object
|
||||
properties:
|
||||
@@ -425,3 +438,46 @@ paths:
|
||||
description: "not found"
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
|
||||
|
||||
/1.0/main/user.json:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Return a list of all user
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
/1.0/main/whoami.json:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Return the currently authenticated user
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
/1.0/main/user/{id}.json:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Return a user by id
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The user id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
|
@@ -5,6 +5,5 @@ services:
|
||||
Chill\MainBundle\Search\SearchProvider: '@chill_main.search_provider'
|
||||
|
||||
Chill\MainBundle\Search\SearchApi:
|
||||
arguments:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
$search: '@Chill\MainBundle\Search\SearchProvider'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@@ -7,6 +7,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
@@ -35,14 +36,18 @@ class AccompanyingCourseController extends Controller
|
||||
|
||||
protected ValidatorInterface $validator;
|
||||
|
||||
private AccompanyingPeriodWorkRepository $workRepository;
|
||||
|
||||
public function __construct(
|
||||
SerializerInterface $serializer,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
ValidatorInterface $validator
|
||||
ValidatorInterface $validator,
|
||||
AccompanyingPeriodWorkRepository $workRepository
|
||||
) {
|
||||
$this->serializer = $serializer;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->validator = $validator;
|
||||
$this->workRepository = $workRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,9 +107,16 @@ class AccompanyingCourseController extends Controller
|
||||
['date' => 'DESC'],
|
||||
);
|
||||
|
||||
$works = $this->workRepository->findByAccompanyingPeriod(
|
||||
$accompanyingCourse,
|
||||
['startDate' => 'DESC', 'endDate' => 'DESC'],
|
||||
3
|
||||
);
|
||||
|
||||
return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [
|
||||
'accompanyingCourse' => $accompanyingCourse,
|
||||
'withoutHousehold' => $withoutHousehold,
|
||||
'works' => $works,
|
||||
'activities' => $activities
|
||||
]);
|
||||
}
|
||||
|
@@ -47,12 +47,12 @@ class AccompanyingPeriodController extends AbstractController
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
|
||||
/**
|
||||
* @var ValidatorInterface
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
|
||||
/**
|
||||
* AccompanyingPeriodController constructor.
|
||||
*
|
||||
@@ -64,23 +64,25 @@ class AccompanyingPeriodController extends AbstractController
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
|
||||
public function listAction(int $person_id): Response
|
||||
{
|
||||
$person = $this->_getPerson($person_id);
|
||||
|
||||
|
||||
$event = new PrivacyEvent($person, [
|
||||
'element_class' => AccompanyingPeriod::class,
|
||||
'action' => 'list'
|
||||
]);
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
|
||||
$accompanyingPeriods = $person->getAccompanyingPeriodsOrdered();
|
||||
|
||||
return $this->render('ChillPersonBundle:AccompanyingPeriod:list.html.twig', [
|
||||
'accompanying_periods' => $person->getAccompanyingPeriodsOrdered(),
|
||||
'accompanying_periods' => $accompanyingPeriods,
|
||||
'person' => $person
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function createAction(int $person_id, Request $request): Response
|
||||
{
|
||||
$person = $this->_getPerson($person_id);
|
||||
@@ -90,17 +92,17 @@ class AccompanyingPeriodController extends AbstractController
|
||||
|
||||
$accompanyingPeriod = new AccompanyingPeriod(new \DateTime('now'));
|
||||
$accompanyingPeriod->setClosingDate(new \DateTime('now'));
|
||||
|
||||
|
||||
$accompanyingPeriod->addPerson($person);
|
||||
//or $person->addAccompanyingPeriod($accompanyingPeriod);
|
||||
|
||||
|
||||
$form = $this->createForm(
|
||||
AccompanyingPeriodType::class,
|
||||
$accompanyingPeriod, [
|
||||
'period_action' => 'create',
|
||||
'center' => $person->getCenter()
|
||||
]);
|
||||
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$form->handleRequest($request);
|
||||
$errors = $this->_validatePerson($person);
|
||||
@@ -120,7 +122,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
$this->generateUrl('chill_person_accompanying_period_list', [
|
||||
'person_id' => $person->getId()
|
||||
]));
|
||||
|
||||
|
||||
} else {
|
||||
$flashBag->add('error', $this->get('translator')
|
||||
->trans('Error! Period not created!'));
|
||||
@@ -137,7 +139,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
'accompanying_period' => $accompanyingPeriod
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -154,7 +156,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
|
||||
/** @var Person $person */
|
||||
$person = $this->_getPerson($person_id);
|
||||
|
||||
|
||||
// CHECK
|
||||
if (! $accompanyingPeriod->containsPerson($person)) {
|
||||
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'])
|
||||
&& count($errors) === 0) {
|
||||
|
||||
|
||||
$em->flush();
|
||||
|
||||
$flashBag->add('success',
|
||||
@@ -186,9 +188,9 @@ class AccompanyingPeriodController extends AbstractController
|
||||
$this->generateUrl('chill_person_accompanying_period_list', [
|
||||
'person_id' => $person->getId()
|
||||
]));
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
$flashBag->add('error', $this->get('translator')
|
||||
->trans('Error when updating the period'));
|
||||
|
||||
@@ -204,19 +206,19 @@ class AccompanyingPeriodController extends AbstractController
|
||||
'accompanying_period' => $accompanyingPeriod
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function closeAction(int $person_id, Request $request): Response
|
||||
{
|
||||
|
||||
|
||||
$person = $this->_getPerson($person_id);
|
||||
|
||||
$this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, 'You are not allowed to update this person');
|
||||
|
||||
if ($person->isOpen() === false) {
|
||||
|
||||
|
||||
$this->get('session')->getFlashBag()
|
||||
->add('error', $this->get('translator')
|
||||
->trans('Beware period is closed', ['%name%' => $person->__toString()]
|
||||
@@ -229,7 +231,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
}
|
||||
|
||||
$current = $person->getCurrentAccompanyingPeriod();
|
||||
|
||||
|
||||
$form = $this->createForm(AccompanyingPeriodType::class, $current, [
|
||||
'period_action' => 'close',
|
||||
'center' => $person->getCenter()
|
||||
@@ -256,7 +258,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
'person_id' => $person->getId()
|
||||
])
|
||||
);
|
||||
|
||||
|
||||
} else {
|
||||
$this->get('session')->getFlashBag()
|
||||
->add('error', $this->get('translator')
|
||||
@@ -267,7 +269,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
->add('info', $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else { //if form is not valid
|
||||
$this->get('session')->getFlashBag()
|
||||
->add('error',
|
||||
@@ -288,7 +290,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
'accompanying_period' => $current
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private function _validatePerson(Person $person): ConstraintViolationListInterface
|
||||
{
|
||||
$errors = $this->validator->validate($person, null,
|
||||
@@ -296,10 +298,10 @@ class AccompanyingPeriodController extends AbstractController
|
||||
|
||||
// Can be disabled with config
|
||||
if (false === $this->container->getParameter('chill_person.allow_multiple_simultaneous_accompanying_periods')) {
|
||||
|
||||
|
||||
$errors_accompanying_period = $this->validator->validate($person, null,
|
||||
['accompanying_period_consistent']);
|
||||
|
||||
|
||||
foreach($errors_accompanying_period as $error ) {
|
||||
$errors->add($error);
|
||||
}
|
||||
@@ -307,7 +309,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
public function openAction(int $person_id, Request $request): Response
|
||||
{
|
||||
$person = $this->_getPerson($person_id);
|
||||
@@ -384,7 +386,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
'accompanying_period' => $accompanyingPeriod
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function reOpenAction(int $person_id, int $period_id, Request $request): Response
|
||||
{
|
||||
/** @var Person $person */
|
||||
@@ -392,7 +394,7 @@ class AccompanyingPeriodController extends AbstractController
|
||||
|
||||
/* @var $period AccompanyingPeriod */
|
||||
$period = \array_filter(
|
||||
$person->getAccompanyingPeriods(),
|
||||
$person->getAccompanyingPeriods(),
|
||||
function (AccompanyingPeriod $p) use ($period_id) {
|
||||
return $p->getId() === ($period_id);
|
||||
}
|
||||
@@ -417,13 +419,13 @@ class AccompanyingPeriodController extends AbstractController
|
||||
return $this->redirectToRoute('chill_person_accompanying_period_list', [
|
||||
'person_id' => $person->getId()
|
||||
]);
|
||||
|
||||
|
||||
} elseif ($confirm === false && $period->canBeReOpened($person)) {
|
||||
return $this->render('ChillPersonBundle:AccompanyingPeriod:re_open.html.twig', [
|
||||
'period' => $period,
|
||||
'person' => $person
|
||||
]);
|
||||
|
||||
|
||||
} else {
|
||||
return (new Response())
|
||||
->setStatusCode(Response::HTTP_BAD_REQUEST)
|
||||
|
@@ -4,15 +4,50 @@ namespace Chill\PersonBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
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\Response;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
|
||||
class HouseholdApiController extends ApiController
|
||||
{
|
||||
private HouseholdRepository $householdRepository;
|
||||
|
||||
public function __construct(HouseholdRepository $householdRepository)
|
||||
{
|
||||
$this->householdRepository = $householdRepository;
|
||||
}
|
||||
|
||||
|
||||
public function householdAddressApi($id, Request $request, string $_format): Response
|
||||
{
|
||||
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"]]);
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ class HouseholdController extends AbstractController
|
||||
$this->translator = $translator;
|
||||
$this->positionRepository = $positionRepository;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{household_id}/summary",
|
||||
@@ -47,7 +47,7 @@ class HouseholdController extends AbstractController
|
||||
->findByActiveOrdered()
|
||||
;
|
||||
|
||||
// little performance improvement:
|
||||
// little performance improvement:
|
||||
// initialize members collection, which will avoid
|
||||
// some queries
|
||||
$household->getMembers()->initialize();
|
||||
@@ -67,6 +67,31 @@ class HouseholdController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{household_id}/accompanying-period",
|
||||
* name="chill_person_household_accompanying_period",
|
||||
* methods={"GET", "HEAD"}
|
||||
* )
|
||||
* @ParamConverter("household", options={"id" = "household_id"})
|
||||
*/
|
||||
public function accompanyingPeriod(Request $request, Household $household)
|
||||
{
|
||||
// TODO ACL
|
||||
|
||||
$members = $household->getMembers();
|
||||
foreach($members as $m) {
|
||||
$accompanyingPeriods = $m->getPerson()->getAccompanyingPeriods();
|
||||
}
|
||||
|
||||
return $this->render('@ChillPerson/Household/accompanying_period.html.twig',
|
||||
[
|
||||
'household' => $household,
|
||||
'accompanying_periods' => $accompanyingPeriods
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{household_id}/addresses",
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Chill\PersonBundle\Controller;
|
||||
|
||||
use Chill\PersonBundle\Entity\Household\Position;
|
||||
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\Household\Household;
|
||||
@@ -25,11 +26,17 @@ class HouseholdMemberController extends ApiController
|
||||
|
||||
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->translator = $translator;
|
||||
$this->periodRepository = $periodRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,8 +151,23 @@ class HouseholdMemberController extends ApiController
|
||||
'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', [
|
||||
'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'
|
||||
],
|
||||
'suggestHouseholdByAccompanyingPeriodParticipation' => [
|
||||
'path' => '/suggest/by-person/{person_id}/through-accompanying-period-participation.{_format}',
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
|
@@ -9,22 +9,22 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class HouseholdMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return [ 'household' ];
|
||||
}
|
||||
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
|
||||
{
|
||||
$household = $parameters['household'];
|
||||
@@ -35,15 +35,22 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
|
||||
'household_id' => $household->getId()
|
||||
]])
|
||||
->setExtras(['order' => 10]);
|
||||
|
||||
|
||||
$menu->addChild($this->translator->trans('household.Accompanying period'), [
|
||||
'route' => 'chill_person_household_accompanying_period',
|
||||
'routeParameters' => [
|
||||
'household_id' => $household->getId()
|
||||
]])
|
||||
->setExtras(['order' => 20]);
|
||||
|
||||
$menu->addChild($this->translator->trans('household.Addresses'), [
|
||||
'route' => 'chill_person_household_addresses',
|
||||
'routeParameters' => [
|
||||
'household_id' => $household->getId()
|
||||
]])
|
||||
->setExtras(['order' => 30]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -25,8 +25,9 @@ namespace Chill\PersonBundle\Repository;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
final class AccompanyingPeriodRepository
|
||||
final class AccompanyingPeriodRepository implements ObjectRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
@@ -34,4 +35,32 @@ final class AccompanyingPeriodRepository
|
||||
{
|
||||
$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;
|
||||
|
||||
use Chill\PersonBundle\Entity\Household\Household;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
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 EntityManagerInterface $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
public function findByIds($ids): array
|
||||
{
|
||||
return $this->repository->findBy(['id' => $ids]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $centers
|
||||
* @param $firstResult
|
||||
|
@@ -1,3 +1,4 @@
|
||||
require('./sass/chillperson.scss');
|
||||
require('./sass/person_with_period.scss');
|
||||
require('./sass/household_banner.scss');
|
||||
require('./sass/accompanying_period_work.scss');
|
||||
|
@@ -46,6 +46,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.short {
|
||||
.item {
|
||||
padding-bottom: 0;
|
||||
ul.record_actions {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.updatedBy {
|
||||
margin-top: 1rem;
|
||||
text-align: right;
|
||||
@@ -94,3 +103,4 @@ ul.timeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,6 @@
|
||||
|
||||
.chill-entity__social-action {
|
||||
.badge-primary {
|
||||
background-color: var(--chill-green);
|
||||
}
|
||||
}
|
@@ -18,9 +18,6 @@
|
||||
|
||||
<dl class="content-bloc" v-if="accompanyingCourse.requestor.type === 'person'">
|
||||
|
||||
<dt>{{ $t('requestor.birthdate') }}</dt>
|
||||
<dd>{{ $d(accompanyingCourse.requestor.birthdate.datetime, 'short') }}</dd>
|
||||
|
||||
<dt>{{ $t('requestor.center') }}</dt>
|
||||
<dd>{{ accompanyingCourse.requestor.center.name }}</dd>
|
||||
|
||||
@@ -118,7 +115,12 @@ export default {
|
||||
get() {
|
||||
return this.$store.state.accompanyingCourse.requestorAnonymous;
|
||||
}
|
||||
}
|
||||
},
|
||||
hasRequestorPersonBirthdate() {
|
||||
console.log('hasRequestorBirthdate');
|
||||
console.log(this.$store.state.accompanyingCourse.requestor);
|
||||
return this.$store.state.accompanyingCourse.requestor.birthdate !== null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeRequestor() {
|
||||
|
@@ -9,11 +9,11 @@
|
||||
</span>
|
||||
{{ resource.resource.text }}
|
||||
</td>
|
||||
|
||||
|
||||
<td v-if="resource.resource.type === 'person'">
|
||||
{{ $tc('person.born') }}{{ $d(resource.resource.birthdate.datetime, 'short') }}
|
||||
</td>
|
||||
<td v-else-if="resource.resource.type === 'thirdparty'">
|
||||
|
||||
</td>
|
||||
<td v-if="resource.resource.type === 'thirdparty'">
|
||||
{{ resource.resource.address.text }}<br>
|
||||
{{ resource.resource.address.postcode.name }}
|
||||
</td>
|
||||
|
@@ -118,11 +118,12 @@ export default {
|
||||
modal.showModal = false;
|
||||
},
|
||||
addToHousehold() {
|
||||
this.$store.dispatch('addDateToAddressAndAddressToHousehold', {
|
||||
householdId: this.householdId,
|
||||
addressId: this.$store.state.newAddress.address_id,
|
||||
body: { validFrom: {datetime: `${this.validFrom}T00:00:00+0100`}}
|
||||
})
|
||||
this.$store.dispatch('addDateToAddressAndAddressToHousehold', {
|
||||
householdId: this.householdId,
|
||||
addressId: this.$store.state.newAddress.address_id,
|
||||
body: { validFrom: {datetime: `${this.validFrom}T00:00:00+0100`}},
|
||||
backUrl: this.backUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -4,7 +4,7 @@ const appMessages = {
|
||||
fr: {
|
||||
select_a_existing_address: 'Sélectionner une adresse existante',
|
||||
create_a_new_address: 'Créer une nouvelle adresse',
|
||||
add_an_address_to_household: 'Déménager le ménage',
|
||||
add_an_address_to_household: 'Enregistrer',
|
||||
validFrom: 'Date du déménagement',
|
||||
move_date: 'Date du déménagement',
|
||||
back_to_the_list: 'Retour à la liste',
|
||||
|
@@ -88,6 +88,7 @@ const store = createStore({
|
||||
commit('addAddressToHousehold', household);
|
||||
commit('setSuccess', true);
|
||||
commit('setLoading', false);
|
||||
window.location.assign(payload.backUrl);
|
||||
resolve();
|
||||
}))
|
||||
.catch((error) => {
|
||||
|
@@ -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 {
|
||||
householdMove,
|
||||
fetchHouseholdSuggestionByAccompanyingPeriod,
|
||||
};
|
||||
|
@@ -14,6 +14,23 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<button class="sc-button bt-create" @click="createHousehold">
|
||||
{{ $t('household_members_editor.household.create_household') }}
|
||||
@@ -30,12 +47,56 @@
|
||||
</button>
|
||||
</li>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue';
|
||||
|
||||
export default {
|
||||
@@ -47,6 +108,12 @@ export default {
|
||||
...mapGetters([
|
||||
'hasHousehold',
|
||||
'isHouseholdNew',
|
||||
'hasHouseholdSuggestion',
|
||||
'countHouseholdSuggestion',
|
||||
'filterHouseholdSuggestionByAccompanyingPeriod',
|
||||
]),
|
||||
...mapState([
|
||||
'showHouseholdSuggestion',
|
||||
]),
|
||||
household() {
|
||||
return this.$store.state.household;
|
||||
@@ -55,6 +122,7 @@ export default {
|
||||
return this.$store.state.allowHouseholdCreate;
|
||||
},
|
||||
allowHouseholdSearch() {
|
||||
return false;
|
||||
return this.$store.state.allowHouseholdSearch;
|
||||
},
|
||||
allowLeaveWithoutHousehold() {
|
||||
@@ -74,6 +142,12 @@ export default {
|
||||
},
|
||||
forceLeaveWithoutHousehold() {
|
||||
this.$store.dispatch('forceLeaveWithoutHousehold');
|
||||
},
|
||||
toggleHouseholdSuggestion() {
|
||||
this.$store.commit('toggleHouseholdSuggestion');
|
||||
},
|
||||
selectHousehold(h) {
|
||||
this.$store.dispatch('selectHousehold', h);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -5,7 +5,7 @@ const appMessages = {
|
||||
fr: {
|
||||
household_members_editor: {
|
||||
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",
|
||||
create_household: "Créer un ménage",
|
||||
search_household: "Chercher un ménage",
|
||||
@@ -13,7 +13,7 @@ const appMessages = {
|
||||
leave_without_household: "Sans nouveau ménage"
|
||||
},
|
||||
concerned: {
|
||||
title: "Usagers concernés",
|
||||
title: "Nouveaux membres du ménage",
|
||||
add_persons: "Ajouter d'autres usagers",
|
||||
search: "Rechercher des usagers",
|
||||
move_to: "Déplacer vers",
|
||||
@@ -29,6 +29,10 @@ const appMessages = {
|
||||
remove_position: "Retirer des {position}",
|
||||
remove_concerned: "Ne plus transférer",
|
||||
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: {
|
||||
start_date: "Début de validité",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { createStore } from 'vuex';
|
||||
import { householdMove, householdMoveTest } from './../api.js';
|
||||
import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod } from './../api.js';
|
||||
import { datetimeToISO } from 'ChillMainAssets/js/date.js';
|
||||
|
||||
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({
|
||||
strict: debug,
|
||||
state: {
|
||||
@@ -33,11 +35,16 @@ const store = createStore({
|
||||
allowHouseholdSearch: window.household_members_editor_data.allowHouseholdSearch,
|
||||
allowLeaveWithoutHousehold: window.household_members_editor_data.allowLeaveWithoutHousehold,
|
||||
forceLeaveWithoutHousehold: false,
|
||||
householdSuggestionByAccompanyingPeriod: [],
|
||||
showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1,
|
||||
warnings: [],
|
||||
errors: []
|
||||
},
|
||||
getters: {
|
||||
isHouseholdNew(state) {
|
||||
if (state.household === null) {
|
||||
return false;
|
||||
}
|
||||
return !Number.isInteger(state.household.id);
|
||||
},
|
||||
hasHousehold(state) {
|
||||
@@ -46,6 +53,21 @@ const store = createStore({
|
||||
hasHouseholdOrLeave(state) {
|
||||
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) {
|
||||
return getters.needsPositionning === false
|
||||
|| (getters.persons.length > 0 && getters.concUnpositionned.length === 0);
|
||||
@@ -172,9 +194,25 @@ const store = createStore({
|
||||
state.household = null;
|
||||
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) {
|
||||
state.startDate = dateI;
|
||||
},
|
||||
toggleHouseholdSuggestion(state) {
|
||||
state.showHouseholdSuggestion = !state.showHouseholdSuggestion;
|
||||
},
|
||||
setWarnings(state, warnings) {
|
||||
state.warnings = warnings;
|
||||
// reset errors, which should come from servers
|
||||
@@ -213,6 +251,10 @@ const store = createStore({
|
||||
commit('forceLeaveWithoutHousehold');
|
||||
dispatch('computeWarnings');
|
||||
},
|
||||
selectHousehold({ commit }, h) {
|
||||
commit('selectHousehold', h);
|
||||
dispatch('computeWarnings');
|
||||
},
|
||||
setStartDate({ commit, dispatch }, date) {
|
||||
commit('setStartDate', date);
|
||||
dispatch('computeWarnings');
|
||||
@@ -220,6 +262,12 @@ const store = createStore({
|
||||
setComment({ commit }, payload) {
|
||||
commit('setComment', payload);
|
||||
},
|
||||
fetchHouseholdSuggestionForConcerned({ commit, state }, person) {
|
||||
fetchHouseholdSuggestionByAccompanyingPeriod(person.id)
|
||||
.then(households => {
|
||||
commit('setHouseholdSuggestionByAccompanyingPeriod', households);
|
||||
});
|
||||
},
|
||||
computeWarnings({ commit, state, getters }) {
|
||||
let warnings = [],
|
||||
payload;
|
||||
@@ -255,7 +303,7 @@ const store = createStore({
|
||||
if (household.type === 'household') {
|
||||
household_id = household.id;
|
||||
// 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 {
|
||||
// we assume the answer was 422...
|
||||
error = household;
|
||||
@@ -274,4 +322,10 @@ const store = createStore({
|
||||
|
||||
store.dispatch('computeWarnings');
|
||||
|
||||
if (concerned.length > 0) {
|
||||
concerned.forEach(c => {
|
||||
store.dispatch('fetchHouseholdSuggestionForConcerned', c.person);
|
||||
});
|
||||
}
|
||||
|
||||
export { store };
|
||||
|
@@ -4,8 +4,12 @@
|
||||
<span class="name">
|
||||
{{ item.result.text }}
|
||||
</span>
|
||||
<span class="birthday">
|
||||
<span class="birthday" v-if="hasBirthdate">
|
||||
{{ $d(item.result.birthdate.datetime, 'short') }}
|
||||
</span>
|
||||
<span class="location" v-if="hasAddress">
|
||||
{{ item.result.current_household_address.text }} -
|
||||
{{ item.result.current_household_address.postcode.name }}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
@@ -29,8 +33,16 @@ import OnTheFly from 'ChillMainAssets/vuejs/_components/OnTheFly.vue';
|
||||
export default {
|
||||
name: 'SuggestionPerson',
|
||||
components: {
|
||||
OnTheFly
|
||||
OnTheFly,
|
||||
},
|
||||
props: ['item']
|
||||
props: ['item'],
|
||||
computed: {
|
||||
hasBirthdate() {
|
||||
return this.item.result.birthdate !== null;
|
||||
},
|
||||
hasAddress() {
|
||||
return this.item.result.current_household_address !== null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -39,12 +39,15 @@
|
||||
<ul>
|
||||
{% for p in withoutHousehold %}
|
||||
<li>
|
||||
<input type="checkbox" name="persons[]" value="{{ p.id }}" />
|
||||
<input type="checkbox" name="persons[]" value="{{ p.id }}" checked />
|
||||
{{ p|chill_entity_render_box }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<input type="hidden" name="expand_suggestions" value="true" />
|
||||
<input type="hidden" name="accompanying_period_id", value="{{ accompanyingCourse.id }}" />
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button type="submit" class="sc-button bt-edit">
|
||||
@@ -91,7 +94,7 @@
|
||||
<li>
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
{%- if p.person.lastAddress is not empty -%}
|
||||
{{ p.person.getLastAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{{ p.person.currentHouseholdAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{%- else -%}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{%- endif -%}
|
||||
@@ -110,7 +113,7 @@
|
||||
) }}"
|
||||
class="sc-button">
|
||||
<i class="fa fa-home"></i>
|
||||
{{ 'household.Household file'|trans }}
|
||||
n° {{ p.person.getCurrentHousehold.id }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -122,111 +125,240 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<h2>{{ 'Requestor'|trans }}</h2>
|
||||
|
||||
{% if accompanyingCourse.requestor == null %}
|
||||
<p>{{ 'Any requestor to this accompanying course'|trans }}</p>
|
||||
{% elseif accompanyingCourse.requestorPerson is not empty %}
|
||||
{% set r = accompanyingCourse.requestorPerson %}
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h3>
|
||||
{{ r.firstname ~ ' ' ~ r.lastname }}
|
||||
<span class="badge badge-pill badge-secondary">{{ 'Usager' }}</span>
|
||||
</h3>
|
||||
<p>
|
||||
{% set born = (r.gender == 'woman') ? 'née': 'né' %}
|
||||
{% set gender = (r.gender == 'woman') ? 'fa-venus' :
|
||||
(r.gender == 'man') ? 'fa-mars' : 'fa-neuter' %}
|
||||
{% set genderTitle = (r.gender == 'woman') ? 'femme' :
|
||||
(r.gender == 'homme') ? 'fa-mars' : 'neutre' %}
|
||||
<i class="fa fa-fw {{ gender }}" title="{{ genderTitle }}"></i>{{ born ~ ' le ' ~ r.birthdate|format_date('short') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
<li>
|
||||
{% if r.mobilenumber %}
|
||||
<i class="fa fa-li fa-mobile"></i><a href="{{ 'tel:' ~ r.mobilenumber }}">{{ r.mobilenumber }}</a>
|
||||
{% else %}
|
||||
<i class="fa fa-li fa-phone"></i>
|
||||
{% if r.phonenumber %}
|
||||
<a href="{{ 'tel:' ~ r.phonenumber }}">{{ r.phonenumber }}</a>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
{%- if r.lastAddress is not empty -%}
|
||||
{{ r.getLastAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{%- else -%}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_person_view', { person_id: r.id }) }}" class="sc-button bt-show" target="_blank" title="Voir"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elseif accompanyingCourse.requestorThirdParty is not empty %}
|
||||
{% set r = accompanyingCourse.requestorThirdParty %}
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h3>
|
||||
{{ r.name }}
|
||||
<span class="badge badge-pill badge-secondary">{{ 'Tiers' }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
<li><i class="fa fa-li fa-envelope-o"></i>
|
||||
<a href="{{ 'mailto:' ~ r.email }}">
|
||||
{{ r.email|chill_print_or_message("thirdparty.No_email") }}
|
||||
</a>
|
||||
</li>
|
||||
<li><i class="fa fa-li fa-phone"></i>
|
||||
{% if r.telephone %}
|
||||
<a href="{{ 'tel:' ~ r.telephone }}">{{ r.telephone }}</a>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li><i class="fa fa-li fa-map-marker"></i>
|
||||
{%- if r.address is not empty -%}
|
||||
{{ r.getAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{%- else -%}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_3party_3party_show', { thirdparty_id: r.id }) }}" class="sc-button bt-show" target="_blank" title="Voir"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>{{ 'Resources'|trans }}</h2>
|
||||
<div class="flex-bloc">
|
||||
{% for r in accompanyingCourse.resources %}
|
||||
<div class="item-bloc">
|
||||
{% if r.person %}
|
||||
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h3>
|
||||
{{ r.person.firstname ~ ' ' ~ r.person.lastname }}
|
||||
<span class="badge badge-pill badge-secondary">{{ 'Usager' }}</span>
|
||||
</h3>
|
||||
<p>
|
||||
{% set born = (r.person.gender == 'woman') ? 'née': 'né' %}
|
||||
{% set gender = (r.person.gender == 'woman') ? 'fa-venus' :
|
||||
(r.person.gender == 'man') ? 'fa-mars' : 'fa-neuter' %}
|
||||
{% set genderTitle = (r.person.gender == 'woman') ? 'femme' :
|
||||
(r.person.gender == 'homme') ? 'fa-mars' : 'neutre' %}
|
||||
<i class="fa fa-fw {{ gender }}" title="{{ genderTitle }}"></i>{{ born ~ ' le ' ~ r.person.birthdate|format_date('short') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
<li>
|
||||
{% if r.person.mobilenumber %}
|
||||
<i class="fa fa-li fa-mobile"></i><a href="{{ 'tel:' ~ r.person.mobilenumber }}">{{ r.person.mobilenumber }}</a>
|
||||
{% else %}
|
||||
<i class="fa fa-li fa-phone"></i>
|
||||
{% if r.person.phonenumber %}
|
||||
<a href="{{ 'tel:' ~ r.person.phonenumber }}">{{ r.person.phonenumber }}</a>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
{%- if r.person.lastAddress is not empty -%}
|
||||
{{ r.person.getLastAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{%- else -%}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_person_view', { person_id: r.person.id }) }}" class="sc-button bt-show" target="_blank" title="Voir"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if accompanyingCourse.resources|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'Any resource for this accompanying course'|trans }}</p>
|
||||
{% else %}
|
||||
<div class="flex-bloc">
|
||||
{% for r in accompanyingCourse.resources %}
|
||||
<div class="item-bloc">
|
||||
{% if r.person %}
|
||||
|
||||
{% endif %}
|
||||
{% if r.thirdParty %}
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h3>
|
||||
{{ r.person.firstname ~ ' ' ~ r.person.lastname }}
|
||||
<span class="badge badge-pill badge-secondary">{{ 'Usager' }}</span>
|
||||
</h3>
|
||||
<p>
|
||||
{% set born = (r.person.gender == 'woman') ? 'née': 'né' %}
|
||||
{% set gender = (r.person.gender == 'woman') ? 'fa-venus' :
|
||||
(r.person.gender == 'man') ? 'fa-mars' : 'fa-neuter' %}
|
||||
{% set genderTitle = (r.person.gender == 'woman') ? 'femme' :
|
||||
(r.person.gender == 'homme') ? 'fa-mars' : 'neutre' %}
|
||||
<i class="fa fa-fw {{ gender }}" title="{{ genderTitle }}"></i>{{ born ~ ' le ' ~ r.person.birthdate|format_date('short') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
<li>
|
||||
{% if r.person.mobilenumber %}
|
||||
<i class="fa fa-li fa-mobile"></i><a href="{{ 'tel:' ~ r.person.mobilenumber }}">{{ r.person.mobilenumber }}</a>
|
||||
{% else %}
|
||||
<i class="fa fa-li fa-phone"></i>
|
||||
{% if r.person.phonenumber %}
|
||||
<a href="{{ 'tel:' ~ r.person.phonenumber }}">{{ r.person.phonenumber }}</a>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
{%- if r.person.lastAddress is not empty -%}
|
||||
{{ r.person.getLastAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{%- else -%}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_person_view', { person_id: r.person.id }) }}" class="sc-button bt-show" target="_blank" title="Voir"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h3>
|
||||
{{ r.thirdParty.name }}
|
||||
<span class="badge badge-pill badge-secondary">{{ 'Tiers' }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
<li><i class="fa fa-li fa-envelope-o"></i>
|
||||
<a href="{{ 'mailto:' ~ r.thirdParty.email }}">
|
||||
{{ r.thirdParty.email|chill_print_or_message("thirdparty.No_email") }}
|
||||
</a>
|
||||
</li>
|
||||
<li><i class="fa fa-li fa-phone"></i>
|
||||
{% if r.thirdParty.telephone %}
|
||||
<a href="{{ 'tel:' ~ r.thirdParty.telephone }}">{{ r.thirdParty.telephone }}</a>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li><i class="fa fa-li fa-map-marker"></i>
|
||||
{%- if r.thirdParty.address is not empty -%}
|
||||
{{ r.thirdParty.getAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{%- else -%}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_3party_3party_show', { thirdparty_id: r.thirdParty.id }) }}" class="sc-button bt-show" target="_blank" title="Voir"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<<<<<<< HEAD
|
||||
{% endif %}
|
||||
{% if r.thirdParty %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h3>
|
||||
{{ r.thirdParty.name }}
|
||||
<span class="badge badge-pill badge-secondary">{{ 'Tiers' }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
<li><i class="fa fa-li fa-envelope-o"></i>
|
||||
<a href="{{ 'mailto:' ~ r.thirdParty.email }}">
|
||||
{{ r.thirdParty.email|chill_print_or_message("thirdparty.No_email") }}
|
||||
</a>
|
||||
</li>
|
||||
<li><i class="fa fa-li fa-phone"></i>
|
||||
{% if r.thirdParty.telephone %}
|
||||
<a href="{{ 'tel:' ~ r.thirdParty.telephone }}">{{ r.thirdParty.telephone }}</a>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li><i class="fa fa-li fa-map-marker"></i>
|
||||
{%- if r.thirdParty.address is not empty -%}
|
||||
{{ r.thirdParty.getAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{%- else -%}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_3party_3party_show', { thirdparty_id: r.thirdParty.id }) }}" class="sc-button bt-show" target="_blank" title="Voir"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>{{ 'Social actions'|trans }}</h2>
|
||||
|
||||
{% set person = null %}
|
||||
<div id="accompanying_course_work_list" class="short">
|
||||
{% for w in works %}
|
||||
<div class="item">
|
||||
<div class="title">
|
||||
<p class="title_label">{{ 'accompanying_course_work.action'|trans }}</p >
|
||||
<h2 class="action_title">
|
||||
{{ w.socialAction|chill_entity_render_box({ 'no-badge': false }) }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a
|
||||
class="sc-button bt-edit"
|
||||
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}">{{ 'Edit'|trans }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% block contentActivity %}
|
||||
{% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'accompanyingCourse', 'context': 'person'} %}
|
||||
{% set person = null %}
|
||||
{% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'accompanyingCourse', 'context': 'accompanyingCourse'} %}
|
||||
{% endblock %}
|
||||
|
||||
{# ==> insert accompanyingCourse vue component #}
|
||||
<div id="accompanying-course"></div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('accompanying_course_work_list') }}
|
||||
{% endblock %}
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<div class="title">
|
||||
<p class="title_label">{{ 'accompanying_course_work.action'|trans }}</p >
|
||||
<h2 class="action_title">
|
||||
{{ w.socialAction|chill_entity_render_box({ 'no-badge': true }) }}
|
||||
{{ w.socialAction|chill_entity_render_box }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<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>
|
||||
<h4 class="goal_title">{{ g.goal.title|localize_translatable_string }}</h4>
|
||||
</div>
|
||||
<div class="results res">
|
||||
{% if g.results|length == 0 %}
|
||||
@@ -96,13 +96,13 @@
|
||||
<li>
|
||||
<a
|
||||
class="sc-button bt-edit"
|
||||
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}">{{ 'Update'|trans }}</a>
|
||||
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}">{{ 'Edit'|trans }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% 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 %}
|
||||
</div>
|
||||
|
||||
|
@@ -0,0 +1,136 @@
|
||||
{% block content %}
|
||||
|
||||
{% for accompanying_period in accompanying_periods %}
|
||||
<div class="flex-table">
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
{{'period'|trans}} <b>#{{ accompanying_period.id }}</b>
|
||||
{% if accompanying_period.emergency %}
|
||||
|
||||
<span class="badge badge-primary">
|
||||
{{- 'Emergency'|trans|upper -}}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if accompanying_period.confidential %}
|
||||
|
||||
<span class="badge badge-primary">
|
||||
{{- 'Confidential'|trans|upper -}}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if accompanying_period.step == 'DRAFT' %}
|
||||
|
||||
<span class="badge badge-primary">
|
||||
{{- 'Draft'|trans|upper -}}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge badge-primary">
|
||||
{{- 'Confirmed'|trans|upper -}}
|
||||
</span>
|
||||
{% endif %}
|
||||
</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 %}
|
||||
{{ 'accompanying_period.dates_from_%opening_date%'|trans({ '%opening_date%': accompanying_period.openingDate|format_date('long') } ) }}
|
||||
{% else %}
|
||||
{{ 'accompanying_period.dates_from_%opening_date%_to_%closing_date%'|trans({
|
||||
'%opening_date%': accompanying_period.openingDate|format_date('long'),
|
||||
'%closing_date%': accompanying_period.closingDate|format_date('long')}
|
||||
) }}
|
||||
|
||||
{% if accompanying_period.isOpen == false %}
|
||||
<dl class="chill_view_data">
|
||||
<dt>{{ 'Closing motive'|trans }} :</dt>
|
||||
<dd>{{ accompanying_period.closingMotive|chill_entity_render_box }}</dd>
|
||||
</dl>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<h3>{{ 'Participants'|trans }}</h3>
|
||||
{% 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>{{ 'Requestor'|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 %}
|
||||
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<h3>{{ 'Social issues'|trans }}</h3>
|
||||
{% if accompanying_period.socialIssues.count > 0 %}
|
||||
{% for si in accompanying_period.socialIssues %}
|
||||
<p>{{ si.title|localize_translatable_string }}</p>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="item-row">
|
||||
<ul class="record_actions">
|
||||
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': accompanying_period.id }) }}" class="sc-button bt-show"></a>
|
||||
</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>
|
||||
</li>
|
||||
{% if accompanying_period.isOpen == true %}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_period_close', {'person_id' : person.id}) }}" class="sc-button bt-update has-hidden change-icon">
|
||||
<i class="fa fa-lock" aria-hidden="true"></i><span class="show-on-hover">{{'Close accompanying period'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if accompanying_period.canBeReOpened(person) == true %}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_period_re_open', {'person_id' : person.id, 'period_id' : accompanying_period.id } ) }}" class="sc-button bt-create change-icon has-hidden">
|
||||
<i class="fa fa-unlock" aria-hidden="true"></i><span class="show-on-hover">{{'Re-open accompanying period'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
#}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endblock content %}
|
@@ -5,114 +5,36 @@
|
||||
{% block title %}{{ 'Person accompanying period - %name%'|trans({ '%name%' : person.__toString}) }}{% endblock title %}
|
||||
|
||||
{% block personcontent %}
|
||||
|
||||
<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>
|
||||
{% include 'ChillPersonBundle:AccompanyingPeriod:_list.html.twig' %}
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for accompanying_period in accompanying_periods %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if accompanying_period.closingDate == null %}
|
||||
{{ 'accompanying_period.dates_from_%opening_date%'|trans({ '%opening_date%': accompanying_period.openingDate|format_date('long') } ) }}
|
||||
{% else %}
|
||||
{{ 'accompanying_period.dates_from_%opening_date%_to_%closing_date%'|trans({
|
||||
'%opening_date%': accompanying_period.openingDate|format_date('long'),
|
||||
'%closing_date%': accompanying_period.closingDate|format_date('long')}
|
||||
) }}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path ('chill_person_view', {'person_id' : person.id } ) }}" class="sc-button bt-cancel">
|
||||
{{ 'Person details'|trans }}
|
||||
</a>
|
||||
</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>
|
||||
<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>
|
||||
</a>
|
||||
</li>
|
||||
{% if person.isOpen == false %}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_period_open', {'person_id' : person.id} ) }}" class="sc-button bt-create change-icon has-hidden">
|
||||
<i class="fa fa-unlock" aria-hidden="true"></i>
|
||||
<span class="show-on-hover">{{'Begin a new accompanying period'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
#}
|
||||
</ul>
|
||||
|
||||
{% if accompanying_period.isOpen == false %}
|
||||
<dl class="chill_view_data">
|
||||
<dt>{{ 'Closing motive'|trans }} :</dt>
|
||||
<dd>{{ accompanying_period.closingMotive|chill_entity_render_box }}</dd>
|
||||
</dl>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if chill_accompanying_periods.fields.user == 'visible' %}
|
||||
<td>
|
||||
{% if accompanying_period.user %}
|
||||
{{ accompanying_period.user.username }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No accompanying user'|trans }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% 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">
|
||||
|
||||
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': accompanying_period.id }) }}" class="sc-button bt-show"></a>
|
||||
</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>
|
||||
</li>
|
||||
{% if accompanying_period.isOpen == true %}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_period_close', {'person_id' : person.id}) }}" class="sc-button bt-update has-hidden change-icon">
|
||||
<i class="fa fa-lock" aria-hidden="true"></i><span class="show-on-hover">{{'Close accompanying period'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if accompanying_period.canBeReOpened(person) == true %}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_period_re_open', {'person_id' : person.id, 'period_id' : accompanying_period.id } ) }}" class="sc-button bt-create change-icon has-hidden">
|
||||
<i class="fa fa-unlock" aria-hidden="true"></i><span class="show-on-hover">{{'Re-open accompanying period'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="form_control">
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<a href="{{ path ('chill_person_view', {'person_id' : person.id } ) }}" class="sc-button bt-cancel">
|
||||
{{ 'Person details'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
</a>
|
||||
</li>
|
||||
{% if person.isOpen == false %}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_period_open', {'person_id' : person.id} ) }}" class="sc-button bt-create change-icon has-hidden">
|
||||
<i class="fa fa-unlock" aria-hidden="true"></i>
|
||||
<span class="show-on-hover">{{'Begin a new accompanying period'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% endblock personcontent %}
|
||||
{% endblock %}
|
||||
|
@@ -0,0 +1,25 @@
|
||||
{% extends '@ChillPerson/Household/layout.html.twig' %}
|
||||
|
||||
{% block title 'Household accompanying period'|trans %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{% include 'ChillPersonBundle:AccompanyingPeriod:_list.html.twig' %}
|
||||
|
||||
<div class="form_control">
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<a href="{{ path ('chill_person_household_summary', {'household_id' : household.id } ) }}" class="sc-button bt-cancel">
|
||||
{{ 'Household summary'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{#<li>
|
||||
<a href="{{ path ('chill_person_household_accompanying_course_new', {'household_id' : [ household.id ] } ) }}" class="sc-button bt-create">
|
||||
{{ 'Create an accompanying period'|trans }}
|
||||
</a>
|
||||
</li>#}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@@ -12,11 +12,11 @@
|
||||
{% else %}
|
||||
<div class="top"><i class="fa fa-caret-up fa-3x"></i></div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% for address in household.addresses %}
|
||||
|
||||
<div class="col-b"></div>
|
||||
|
||||
|
||||
<div class="col-c content{{ ' row' ~ loop.index }}">
|
||||
<div class="address">
|
||||
{% if address.isNoAddress == true %}
|
||||
@@ -39,22 +39,22 @@
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
|
||||
{#<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_person_household_address_edit', { 'household_id': household.id, 'address_id' : address.id } ) }}" class="sc-button bt-edit"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul> #}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="date">
|
||||
{% if address.validFrom is not empty %}
|
||||
{{ address.validFrom|format_date('long') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
|
@@ -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 %}
|
||||
|
||||
@@ -14,6 +15,7 @@
|
||||
{% block js %}
|
||||
<script type="text/javascript">
|
||||
window.household_members_editor_data = {{ data|json_encode|raw }};
|
||||
window.household_members_editor_expand_suggestions = {{ expandSuggestions }};
|
||||
</script>
|
||||
{{ encore_entry_script_tags('household_members_editor') }}
|
||||
{% endblock %}
|
||||
|
@@ -16,8 +16,8 @@
|
||||
#}
|
||||
<ul class="tab-nav">
|
||||
{% for menu in menus %}
|
||||
<li class="">
|
||||
<li class="{{ menu.extra('class') }}">
|
||||
<a href="{{ menu.uri }}" >{{ menu.label|upper }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</ul>
|
||||
|
@@ -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:
|
||||
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:
|
||||
post:
|
||||
tags:
|
||||
|
@@ -46,6 +46,7 @@ services:
|
||||
$serializer: '@Symfony\Component\Serializer\SerializerInterface'
|
||||
$dispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface'
|
||||
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
|
||||
$workRepository: '@Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository'
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
Chill\PersonBundle\Controller\AccompanyingCourseApiController:
|
||||
|
@@ -28,3 +28,7 @@ services:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
||||
|
||||
Chill\PersonBundle\Search\SearchPersonApiProvider:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@@ -38,12 +38,13 @@ household:
|
||||
Edit member household: Modifier l'appartenance au ménage
|
||||
Current household members: Membres actuels
|
||||
Household summary: Résumé
|
||||
Accompanying period: Parcours d'accompagnement
|
||||
Addresses: Adresses
|
||||
Current address: Adresse actuelle
|
||||
Household does not have any address currently: Le ménage n'a pas d'adresse renseignée actuellement
|
||||
Edit household members: Modifier l'appartenance au ménage
|
||||
and x other persons: >-
|
||||
{x, plural,
|
||||
{x, plural,
|
||||
one {et une autre personne}
|
||||
many {et # autres personnes}
|
||||
other {et # autres personnes}
|
||||
@@ -59,7 +60,7 @@ household:
|
||||
data_saved: Données enregistrées
|
||||
Household history for %name%: Historique des ménages pour {name}
|
||||
Household shared: Ménages domiciliés
|
||||
Household not shared: Ménage non domiciliés
|
||||
Household not shared: Ménage non domiciliés
|
||||
Never in any household: Membre d'aucun ménage
|
||||
Membership currently running: En cours
|
||||
from: Depuis
|
||||
|
@@ -165,6 +165,8 @@ An accompanying period starts: Une période d'accompagnement est ouverte
|
||||
Any accompanying periods are open: Aucune période d'accompagnement ouverte
|
||||
An accompanying period is open: Une période d'accompagnement est ouverte
|
||||
Accompanying period list: Périodes d'accompagnement
|
||||
Accompanying period: Parcours d'accompagnement
|
||||
period: Parcours
|
||||
New accompanying course: Nouveau parcours d'accompagnement
|
||||
Choose a motive: Motif de fermeture
|
||||
Re-open accompanying period: Ré-ouvrir
|
||||
@@ -180,6 +182,8 @@ Create an accompanying course: Créer un parcours
|
||||
This accompanying course is still a draft: Ce parcours est à l'état brouillon
|
||||
Associated peoples: Usagers concernés
|
||||
Resources: Interlocuteurs privilégiés
|
||||
Requestor: Demandeur
|
||||
Any requestor to this accompanying course: Aucun demandeur pour ce parcours
|
||||
Social actions: Actions d'accompagnement
|
||||
Social issues: Problématiques sociales
|
||||
Last events on accompanying course: Dernières actions de suivi
|
||||
@@ -189,6 +193,7 @@ See accompanying period: Voir cette période d'accompagnement
|
||||
Referrer: Référent
|
||||
Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur appartenance à un ménage dès que possible.
|
||||
Add to household now: Ajouter à un ménage
|
||||
Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours
|
||||
|
||||
# pickAPersonType
|
||||
Pick a person: Choisir une personne
|
||||
@@ -320,6 +325,9 @@ accompanying_period:
|
||||
dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date%
|
||||
occasional: ponctuel
|
||||
regular: régulier
|
||||
Confidential: confidentiel
|
||||
Draft: brouillon
|
||||
Confirmed: en file active
|
||||
|
||||
# Accompanying Course
|
||||
Accompanying Course: Parcours d'accompagnement
|
||||
@@ -337,6 +345,8 @@ Members: Membres
|
||||
Addresses: Adresses
|
||||
Move household: Nouveau déménagement
|
||||
Addresses history for household: Historique des adresses
|
||||
Household accompanying period: Parcours d'accompagnement du ménage
|
||||
Household summary: Résumé du ménage
|
||||
|
||||
# accompanying course work
|
||||
Accompanying Course Actions: Actions d'accompagnements
|
||||
@@ -353,4 +363,5 @@ accompanying_course_work:
|
||||
no_results: Aucun résultat - orientation
|
||||
results: Résultats - orientations
|
||||
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'
|
||||
tags:
|
||||
- { name: 'chill.search', alias: '3party' }
|
||||
|
||||
Chill\ThirdPartyBundle\Search\ThirdPartyApiSearch:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
Reference in New Issue
Block a user