mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-10 07:19:42 +00:00
Compare commits
7 Commits
375-notifi
...
385-invita
Author | SHA1 | Date | |
---|---|---|---|
74c9eb5585 | |||
f93c7e014f | |||
e6a799abc4 | |||
68a0ef7115 | |||
1675c56f3d | |||
675e8450fc | |||
4ffd7034d0 |
6
.changes/unreleased/Feature-20250808-120802.yaml
Normal file
6
.changes/unreleased/Feature-20250808-120802.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: Create invitation list in user menu
|
||||||
|
time: 2025-08-08T12:08:02.446361367+02:00
|
||||||
|
custom:
|
||||||
|
Issue: "385"
|
||||||
|
SchemaChange: No schema change
|
@@ -266,7 +266,7 @@ class CalendarController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->getUser() instanceof User) {
|
if (!$this->getUser() instanceof User) {
|
||||||
throw new UnauthorizedHttpException('you are not an user');
|
throw new UnauthorizedHttpException('you are not a user');
|
||||||
}
|
}
|
||||||
|
|
||||||
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
|
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
|
||||||
|
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\CalendarBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\CalendarBundle\Repository\InviteRepository;
|
||||||
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class MyInvitationsController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(private readonly InviteRepository $inviteRepository, private readonly PaginatorFactory $paginator, private readonly DocGeneratorTemplateRepositoryInterface $docGeneratorTemplateRepository) {}
|
||||||
|
|
||||||
|
#[Route(path: '/{_locale}/calendar/invitations/my', name: 'chill_calendar_invitations_list_my')]
|
||||||
|
public function myInvitations(Request $request): Response
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||||
|
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new UnauthorizedHttpException('you are not a user');
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = count($this->inviteRepository->findBy(['user' => $user]));
|
||||||
|
$paginator = $this->paginator->create($total);
|
||||||
|
|
||||||
|
$invitations = $this->inviteRepository->findBy(
|
||||||
|
['user' => $user],
|
||||||
|
['createdAt' => 'DESC'],
|
||||||
|
$paginator->getItemsPerPage(),
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()
|
||||||
|
);
|
||||||
|
|
||||||
|
$view = '@ChillCalendar/Invitations/listByUser.html.twig';
|
||||||
|
|
||||||
|
return $this->render($view, [
|
||||||
|
'invitations' => $invitations,
|
||||||
|
'paginator' => $paginator,
|
||||||
|
'templates' => $this->docGeneratorTemplateRepository->findByEntity(Calendar::class),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@@ -30,6 +30,13 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
'order' => 9,
|
'order' => 9,
|
||||||
'icon' => 'tasks',
|
'icon' => 'tasks',
|
||||||
]);
|
]);
|
||||||
|
$menu->addChild('My invitations list', [
|
||||||
|
'route' => 'chill_calendar_invitations_list_my',
|
||||||
|
])
|
||||||
|
->setExtras([
|
||||||
|
'order' => 9,
|
||||||
|
'icon' => 'tasks',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -41,7 +41,7 @@ class InviteRepository implements ObjectRepository
|
|||||||
/**
|
/**
|
||||||
* @return array|Invite[]
|
* @return array|Invite[]
|
||||||
*/
|
*/
|
||||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null)
|
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||||
{
|
{
|
||||||
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
@@ -1,240 +1,229 @@
|
|||||||
{# list used in context of person or accompanyingPeriod #}
|
{# list used in context of person, accompanyingPeriod or user #}
|
||||||
|
|
||||||
{% if calendarItems|length > 0 %}
|
<div class="item-bloc">
|
||||||
<div class="flex-table list-records context-accompanyingCourse">
|
<div class="item-row main">
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="wrap-header">
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<p class="date-label">
|
||||||
|
{% if context == 'person' and calendar.context == 'accompanying_period' %}
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;">
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fa fa-random"></i> {{ calendar.accompanyingPeriod.id }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
||||||
|
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||||
|
- {{ calendar.endDate|format_datetime('short', 'short') }}
|
||||||
|
{% else %}
|
||||||
|
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||||
|
- {{ calendar.endDate|format_datetime('none', 'short') }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
{% for calendar in calendarItems %}
|
<div class="duration short-message">
|
||||||
|
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||||
<div class="item-bloc">
|
{{ calendar.duration|date('%H:%I') }}
|
||||||
<div class="item-row main">
|
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
|
||||||
<div class="item-col">
|
<!-- no sms will be send -->
|
||||||
<div class="wrap-header">
|
{% else %}
|
||||||
<div class="wl-row">
|
{% if calendar.smsStatus == 'sms_sent' %}
|
||||||
<div class="wl-col title">
|
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
|
||||||
<p class="date-label">
|
<i class="fa fa-check "></i>
|
||||||
{% if context == 'person' and calendar.context == 'accompanying_period' %}
|
<i class="fa fa-envelope "></i>
|
||||||
<a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;">
|
</span>
|
||||||
<span class="badge bg-primary">
|
{% else %}
|
||||||
<i class="fa fa-random"></i> {{ calendar.accompanyingPeriod.id }}
|
<span title="{{ 'Will send SMS'|trans }}" class="badge bg-info">
|
||||||
</span>
|
<i class="fa fa-envelope "></i>
|
||||||
</a>
|
<i class="fa fa-hourglass-end "></i>
|
||||||
{% endif %}
|
</span>
|
||||||
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
{% endif %}
|
||||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
{% endif %}
|
||||||
- {{ calendar.endDate|format_datetime('short', 'short') }}
|
|
||||||
{% else %}
|
|
||||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
|
||||||
- {{ calendar.endDate|format_datetime('none', 'short') }}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="duration short-message">
|
|
||||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
|
||||||
{{ calendar.duration|date('%H:%I') }}
|
|
||||||
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
|
|
||||||
<!-- no sms will be send -->
|
|
||||||
{% else %}
|
|
||||||
{% if calendar.smsStatus == 'sms_sent' %}
|
|
||||||
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
|
|
||||||
<i class="fa fa-check "></i>
|
|
||||||
<i class="fa fa-envelope "></i>
|
|
||||||
</span>
|
|
||||||
{% else %}
|
|
||||||
<span title="{{ 'Will send SMS'|trans }}" class="badge bg-info">
|
|
||||||
<i class="fa fa-envelope "></i>
|
|
||||||
<i class="fa fa-hourglass-end "></i>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item-col">
|
</div>
|
||||||
<ul class="list-content">
|
</div>
|
||||||
{% if calendar.mainUser is not empty %}
|
</div>
|
||||||
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
|
|
||||||
|
<div class="item-col">
|
||||||
|
<ul class="list-content">
|
||||||
|
{% if calendar.mainUser is not empty %}
|
||||||
|
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if calendar.comment.comment is not empty
|
||||||
|
or calendar.users|length > 0
|
||||||
|
or calendar.thirdParties|length > 0
|
||||||
|
or calendar.users|length > 0 %}
|
||||||
|
<div class="item-row details separator">
|
||||||
|
<div class="item-col">
|
||||||
|
{% include '@ChillActivity/Activity/concernedGroups.html.twig' with {
|
||||||
|
'context': calendar.context == 'person' ? 'calendar_person' : 'calendar_accompanyingCourse',
|
||||||
|
'render': 'wrap-list',
|
||||||
|
'entity': calendar
|
||||||
|
} %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if calendar.comment.comment is not empty %}
|
||||||
|
<div class="item-row details separator">
|
||||||
|
<div class="item-col comment">
|
||||||
|
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if calendar.location is not empty %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div>
|
||||||
|
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
|
||||||
|
<i class="fa fa-map-marker"></i>{% endif %}
|
||||||
|
{% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
|
||||||
|
{% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
|
||||||
|
<i class="fa fa-map-marker"></i>{% endif %}
|
||||||
|
{% if calendar.location.phonenumber1 is not empty %}<i
|
||||||
|
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
|
||||||
|
{% if calendar.location.phonenumber2 is not empty %}<i
|
||||||
|
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="item-row separator column">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if calendar.activity is not null %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="wrap-list">
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title"><h3>{{ 'Activity'|trans }}</h3></div>
|
||||||
|
<div class="wl-col list activity-linked">
|
||||||
|
<h2 class="badge-title">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">
|
||||||
|
{{ calendar.activity.type.name | localize_translatable_string }}
|
||||||
|
|
||||||
|
{% if calendar.activity.emergency %}
|
||||||
|
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li class="cancel">
|
||||||
|
<span class="createdBy">
|
||||||
|
{{ 'Created by'|trans }}
|
||||||
|
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': calendar.activity.id}) }}" class="btn btn-sm btn-show" ></a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if calendar.comment.comment is not empty
|
|
||||||
or calendar.users|length > 0
|
|
||||||
or calendar.thirdParties|length > 0
|
|
||||||
or calendar.users|length > 0 %}
|
|
||||||
<div class="item-row details separator">
|
|
||||||
<div class="item-col">
|
|
||||||
{% include '@ChillActivity/Activity/concernedGroups.html.twig' with {
|
|
||||||
'context': calendar.context == 'person' ? 'calendar_person' : 'calendar_accompanyingCourse',
|
|
||||||
'render': 'wrap-list',
|
|
||||||
'entity': calendar
|
|
||||||
} %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if calendar.comment.comment is not empty %}
|
|
||||||
<div class="item-row details separator">
|
|
||||||
<div class="item-col comment">
|
|
||||||
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if calendar.location is not empty %}
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div>
|
|
||||||
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
|
|
||||||
<i class="fa fa-map-marker"></i>{% endif %}
|
|
||||||
{% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
|
|
||||||
{% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
|
|
||||||
<i class="fa fa-map-marker"></i>{% endif %}
|
|
||||||
{% if calendar.location.phonenumber1 is not empty %}<i
|
|
||||||
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
|
|
||||||
{% if calendar.location.phonenumber2 is not empty %}<i
|
|
||||||
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="item-row separator column">
|
|
||||||
<div>
|
|
||||||
|
|
||||||
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if calendar.activity is not null %}
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="item-col">
|
|
||||||
<div class="wrap-list">
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title"><h3>{{ 'Activity'|trans }}</h3></div>
|
|
||||||
<div class="wl-col list activity-linked">
|
|
||||||
<h2 class="badge-title">
|
|
||||||
<span class="title_label"></span>
|
|
||||||
<span class="title_action">
|
|
||||||
{{ calendar.activity.type.name | localize_translatable_string }}
|
|
||||||
|
|
||||||
{% if calendar.activity.emergency %}
|
|
||||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<ul class="record_actions">
|
|
||||||
<li class="cancel">
|
|
||||||
<span class="createdBy">
|
|
||||||
{{ 'Created by'|trans }}
|
|
||||||
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': calendar.activity.id}) }}" class="btn btn-sm btn-show" ></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
|
||||||
<ul class="record_actions">
|
|
||||||
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %}
|
|
||||||
{% if templates|length == 0 %}
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-create"
|
|
||||||
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
|
||||||
{{ 'chill_calendar.Add a document'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li>
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="btn btn-create dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
{{ 'chill_calendar.Add a document'|trans }}
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item"
|
|
||||||
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
|
||||||
{{ 'chill_calendar.Upload a document'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% for template in templates %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item"
|
|
||||||
href="{{ chill_path_add_return_path('chill_docgenerator_generate_from_template', {'template': template.id, 'entityClassName': 'Chill\\CalendarBundle\\Entity\\Calendar', 'entityId': calendar.id}) }}"
|
|
||||||
>
|
|
||||||
{{ template.name|localize_translatable_string }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if calendar.activity is null and (
|
|
||||||
(calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
|
|
||||||
or
|
|
||||||
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
|
|
||||||
)
|
|
||||||
%}
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-create"
|
|
||||||
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
|
|
||||||
{{ 'Transform to activity'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if (calendar.isInvited(app.user)) %}
|
|
||||||
{% set invite = calendar.inviteForUser(app.user) %}
|
|
||||||
<li>
|
|
||||||
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
|
|
||||||
data-calendar-id="{{ calendar.id|e('html_attr') }}"></div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if false %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_show', { 'id': calendar.id}) }}"
|
|
||||||
class="btn btn-show "></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
|
|
||||||
class="btn btn-update "></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}"
|
|
||||||
class="btn btn-delete "></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if calendarItems|length < paginator.getTotalItems %}
|
<div class="item-row separator">
|
||||||
{{ chill_pagination(paginator) }}
|
<ul class="record_actions">
|
||||||
{% endif %}
|
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %}
|
||||||
|
{% if templates|length == 0 %}
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-create"
|
||||||
|
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
||||||
|
{{ 'chill_calendar.Add a document'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li>
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-create dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
{{ 'chill_calendar.Add a document'|trans }}
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item"
|
||||||
|
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
||||||
|
{{ 'chill_calendar.Upload a document'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% for template in templates %}
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item"
|
||||||
|
href="{{ chill_path_add_return_path('chill_docgenerator_generate_from_template', {'template': template.id, 'entityClassName': 'Chill\\CalendarBundle\\Entity\\Calendar', 'entityId': calendar.id}) }}"
|
||||||
|
>
|
||||||
|
{{ template.name|localize_translatable_string }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if calendar.activity is null and (
|
||||||
|
(calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
|
||||||
|
or
|
||||||
|
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
|
||||||
|
)
|
||||||
|
%}
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-create"
|
||||||
|
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
|
||||||
|
{{ 'Transform to activity'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if (calendar.isInvited(app.user)) %}
|
||||||
|
{% set invite = calendar.inviteForUser(app.user) %}
|
||||||
|
<li>
|
||||||
|
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
|
||||||
|
data-calendar-id="{{ calendar.id|e('html_attr') }}"></div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if false %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_show', { 'id': calendar.id}) }}"
|
||||||
|
class="btn btn-show "></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
|
||||||
|
class="btn btn-update "></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}"
|
||||||
|
class="btn btn-delete "></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@@ -34,7 +34,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
|
{% if calendarItems|length > 0 %}
|
||||||
|
<div class="flex-table list-records context-accompanyingCourse">
|
||||||
|
{% for calendar in calendarItems %}
|
||||||
|
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if calendarItems|length < paginator.getTotalItems %}
|
||||||
|
{{ chill_pagination(paginator) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
@@ -33,7 +33,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
|
{% if calendarItems|length > 0 %}
|
||||||
|
<div class="flex-table list-records context-person">
|
||||||
|
{% for calendar in calendarItems %}
|
||||||
|
{{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if calendarItems|length < paginator.getTotalItems %}
|
||||||
|
{{ chill_pagination(paginator) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
@@ -0,0 +1,40 @@
|
|||||||
|
{% extends "@ChillMain/layout.html.twig" %}
|
||||||
|
|
||||||
|
{% set activeRouteKey = 'chill_calendar_invitations_list' %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'My invitations list' |trans }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{{ 'invite.list.title'|trans }}</h1>
|
||||||
|
|
||||||
|
{% if invitations|length == 0 %}
|
||||||
|
<p class="chill-no-data-statement">
|
||||||
|
{{ "invite.list.none"|trans }}
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<div class="flex-table list-records">
|
||||||
|
{% for invitation in invitations %}
|
||||||
|
{% set calendar = invitation.getCalendar %}
|
||||||
|
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'user'}) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if invitations|length < paginator.getTotalItems %}
|
||||||
|
{{ chill_pagination(paginator) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_script_tags('mod_answer') }}
|
||||||
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('mod_answer') }}
|
||||||
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
|
{% endblock %}
|
@@ -0,0 +1,292 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\CalendarBundle\Tests\Controller;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Controller\MyInvitationsController;
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\CalendarBundle\Entity\Invite;
|
||||||
|
use Chill\CalendarBundle\Repository\InviteRepository;
|
||||||
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class MyInvitationsControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
private MyInvitationsController $controller;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
// Create prophecies for dependencies
|
||||||
|
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||||
|
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||||
|
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||||
|
|
||||||
|
// Create controller instance
|
||||||
|
$this->controller = new MyInvitationsController(
|
||||||
|
$inviteRepository->reveal(),
|
||||||
|
$paginatorFactory->reveal(),
|
||||||
|
$docGeneratorTemplateRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up necessary services for AbstractController
|
||||||
|
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||||
|
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||||
|
$twig = $this->prophesize(Environment::class);
|
||||||
|
|
||||||
|
// Use reflection to set the container
|
||||||
|
$reflection = new \ReflectionClass($this->controller);
|
||||||
|
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||||
|
$containerProperty->setAccessible(true);
|
||||||
|
|
||||||
|
// Create a mock container
|
||||||
|
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||||
|
$container->has('security.authorization_checker')->willReturn(true);
|
||||||
|
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||||
|
$container->has('security.token_storage')->willReturn(true);
|
||||||
|
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||||
|
$container->has('twig')->willReturn(true);
|
||||||
|
$container->get('twig')->willReturn($twig->reveal());
|
||||||
|
|
||||||
|
$containerProperty->setValue($this->controller, $container->reveal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMyInvitationsReturnsCorrectAmountOfInvitations(): void
|
||||||
|
{
|
||||||
|
// Create test user
|
||||||
|
$user = new User();
|
||||||
|
$user->setUsername('testuser');
|
||||||
|
|
||||||
|
// Create test invitations
|
||||||
|
$invite1 = new Invite();
|
||||||
|
$invite1->setUser($user);
|
||||||
|
$invite1->setStatus(Invite::PENDING);
|
||||||
|
|
||||||
|
$invite2 = new Invite();
|
||||||
|
$invite2->setUser($user);
|
||||||
|
$invite2->setStatus(Invite::ACCEPTED);
|
||||||
|
|
||||||
|
$invite3 = new Invite();
|
||||||
|
$invite3->setUser($user);
|
||||||
|
$invite3->setStatus(Invite::DECLINED);
|
||||||
|
|
||||||
|
$allInvitations = [$invite1, $invite2, $invite3];
|
||||||
|
$paginatedInvitations = [$invite1, $invite2]; // First page with 2 items per page
|
||||||
|
|
||||||
|
// Set up repository prophecies
|
||||||
|
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||||
|
$inviteRepository->findBy(['user' => $user])->willReturn($allInvitations);
|
||||||
|
$inviteRepository->findBy(
|
||||||
|
['user' => $user],
|
||||||
|
['createdAt' => 'DESC'],
|
||||||
|
2, // items per page
|
||||||
|
0 // offset
|
||||||
|
)->willReturn($paginatedInvitations);
|
||||||
|
|
||||||
|
// Set up paginator prophecies
|
||||||
|
$paginator = $this->prophesize(PaginatorInterface::class);
|
||||||
|
$paginator->getItemsPerPage()->willReturn(2);
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()->willReturn(0);
|
||||||
|
|
||||||
|
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||||
|
$paginatorFactory->create(3)->willReturn($paginator->reveal());
|
||||||
|
|
||||||
|
// Set up doc generator repository
|
||||||
|
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||||
|
$docGeneratorTemplateRepository->findByEntity(Calendar::class)->willReturn([]);
|
||||||
|
|
||||||
|
// Create controller with mocked dependencies
|
||||||
|
$controller = new MyInvitationsController(
|
||||||
|
$inviteRepository->reveal(),
|
||||||
|
$paginatorFactory->reveal(),
|
||||||
|
$docGeneratorTemplateRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up authorization checker to return true for ROLE_USER
|
||||||
|
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||||
|
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(true);
|
||||||
|
|
||||||
|
// Set up token storage to return user
|
||||||
|
$token = $this->prophesize(TokenInterface::class);
|
||||||
|
$token->getUser()->willReturn($user);
|
||||||
|
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||||
|
$tokenStorage->getToken()->willReturn($token->reveal());
|
||||||
|
|
||||||
|
// Set up twig to return a response
|
||||||
|
$twig = $this->prophesize(Environment::class);
|
||||||
|
$twig->render('@ChillCalendar/Invitations/listByUser.html.twig', [
|
||||||
|
'invitations' => $paginatedInvitations,
|
||||||
|
'paginator' => $paginator->reveal(),
|
||||||
|
'templates' => [],
|
||||||
|
])->willReturn('rendered content');
|
||||||
|
|
||||||
|
// Set up container
|
||||||
|
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||||
|
$container->has('security.authorization_checker')->willReturn(true);
|
||||||
|
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||||
|
$container->has('security.token_storage')->willReturn(true);
|
||||||
|
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||||
|
$container->has('twig')->willReturn(true);
|
||||||
|
$container->get('twig')->willReturn($twig->reveal());
|
||||||
|
|
||||||
|
// Use reflection to set the container
|
||||||
|
$reflection = new \ReflectionClass($controller);
|
||||||
|
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||||
|
$containerProperty->setAccessible(true);
|
||||||
|
$containerProperty->setValue($controller, $container->reveal());
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
// Execute the action
|
||||||
|
$response = $controller->myInvitations($request);
|
||||||
|
|
||||||
|
// Assert that response is successful
|
||||||
|
self::assertInstanceOf(Response::class, $response);
|
||||||
|
self::assertSame(200, $response->getStatusCode());
|
||||||
|
self::assertSame('rendered content', $response->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMyInvitationsPageLoads(): void
|
||||||
|
{
|
||||||
|
// Create test user
|
||||||
|
$user = new User();
|
||||||
|
$user->setUsername('testuser');
|
||||||
|
|
||||||
|
// Set up repository prophecies - no invitations
|
||||||
|
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||||
|
$inviteRepository->findBy(['user' => $user])->willReturn([]);
|
||||||
|
$inviteRepository->findBy(
|
||||||
|
['user' => $user],
|
||||||
|
['createdAt' => 'DESC'],
|
||||||
|
20, // default items per page
|
||||||
|
0 // offset
|
||||||
|
)->willReturn([]);
|
||||||
|
|
||||||
|
// Set up paginator prophecies
|
||||||
|
$paginator = $this->prophesize(PaginatorInterface::class);
|
||||||
|
$paginator->getItemsPerPage()->willReturn(20);
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()->willReturn(0);
|
||||||
|
|
||||||
|
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||||
|
$paginatorFactory->create(0)->willReturn($paginator->reveal());
|
||||||
|
|
||||||
|
// Set up doc generator repository
|
||||||
|
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||||
|
$docGeneratorTemplateRepository->findByEntity(Calendar::class)->willReturn([]);
|
||||||
|
|
||||||
|
// Create controller with mocked dependencies
|
||||||
|
$controller = new MyInvitationsController(
|
||||||
|
$inviteRepository->reveal(),
|
||||||
|
$paginatorFactory->reveal(),
|
||||||
|
$docGeneratorTemplateRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up authorization checker to return true for ROLE_USER
|
||||||
|
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||||
|
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(true);
|
||||||
|
|
||||||
|
// Set up token storage to return user
|
||||||
|
$token = $this->prophesize(TokenInterface::class);
|
||||||
|
$token->getUser()->willReturn($user);
|
||||||
|
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||||
|
$tokenStorage->getToken()->willReturn($token->reveal());
|
||||||
|
|
||||||
|
// Set up twig to return a response
|
||||||
|
$twig = $this->prophesize(Environment::class);
|
||||||
|
$twig->render('@ChillCalendar/Invitations/listByUser.html.twig', [
|
||||||
|
'invitations' => [],
|
||||||
|
'paginator' => $paginator->reveal(),
|
||||||
|
'templates' => [],
|
||||||
|
])->willReturn('empty page content');
|
||||||
|
|
||||||
|
// Set up container
|
||||||
|
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||||
|
$container->has('security.authorization_checker')->willReturn(true);
|
||||||
|
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||||
|
$container->has('security.token_storage')->willReturn(true);
|
||||||
|
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||||
|
$container->has('twig')->willReturn(true);
|
||||||
|
$container->get('twig')->willReturn($twig->reveal());
|
||||||
|
|
||||||
|
// Use reflection to set the container
|
||||||
|
$reflection = new \ReflectionClass($controller);
|
||||||
|
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||||
|
$containerProperty->setAccessible(true);
|
||||||
|
$containerProperty->setValue($controller, $container->reveal());
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
// Execute the action
|
||||||
|
$response = $controller->myInvitations($request);
|
||||||
|
|
||||||
|
// Assert that page loads successfully
|
||||||
|
self::assertInstanceOf(Response::class, $response);
|
||||||
|
self::assertSame(200, $response->getStatusCode());
|
||||||
|
self::assertSame('empty page content', $response->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMyInvitationsRequiresAuthentication(): void
|
||||||
|
{
|
||||||
|
// Create controller with minimal dependencies
|
||||||
|
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||||
|
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||||
|
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||||
|
|
||||||
|
$controller = new MyInvitationsController(
|
||||||
|
$inviteRepository->reveal(),
|
||||||
|
$paginatorFactory->reveal(),
|
||||||
|
$docGeneratorTemplateRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up authorization checker to return false for ROLE_USER
|
||||||
|
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||||
|
$authorizationChecker->isGranted('ROLE_USER')->willReturn(false);
|
||||||
|
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(false);
|
||||||
|
|
||||||
|
// Set up container
|
||||||
|
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||||
|
$container->has('security.authorization_checker')->willReturn(true);
|
||||||
|
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||||
|
|
||||||
|
// Use reflection to set the container
|
||||||
|
$reflection = new \ReflectionClass($controller);
|
||||||
|
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||||
|
$containerProperty->setAccessible(true);
|
||||||
|
$containerProperty->setValue($controller, $container->reveal());
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
// Expect AccessDeniedException
|
||||||
|
$this->expectException(\Symfony\Component\Security\Core\Exception\AccessDeniedException::class);
|
||||||
|
|
||||||
|
// Execute the action
|
||||||
|
$controller->myInvitations($request);
|
||||||
|
}
|
||||||
|
}
|
@@ -86,6 +86,9 @@ invite:
|
|||||||
declined: Refusé
|
declined: Refusé
|
||||||
pending: En attente
|
pending: En attente
|
||||||
tentative: Accepté provisoirement
|
tentative: Accepté provisoirement
|
||||||
|
list:
|
||||||
|
none: Il n'y aucun invitation
|
||||||
|
title: Mes invitations
|
||||||
|
|
||||||
# exports
|
# exports
|
||||||
Exports of calendar: Exports des rendez-vous
|
Exports of calendar: Exports des rendez-vous
|
||||||
|
@@ -20,4 +20,9 @@ use Doctrine\Persistence\ObjectRepository;
|
|||||||
interface DocGeneratorTemplateRepositoryInterface extends ObjectRepository
|
interface DocGeneratorTemplateRepositoryInterface extends ObjectRepository
|
||||||
{
|
{
|
||||||
public function countByEntity(string $entity): int;
|
public function countByEntity(string $entity): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|DocGeneratorTemplate[]
|
||||||
|
*/
|
||||||
|
public function findByEntity(string $entity, ?int $start = 0, ?int $limit = 50): array;
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@ use Symfony\Component\Routing\RouterInterface;
|
|||||||
/**
|
/**
|
||||||
* Create paginator instances.
|
* Create paginator instances.
|
||||||
*/
|
*/
|
||||||
final readonly class PaginatorFactory implements PaginatorFactoryInterface
|
class PaginatorFactory implements PaginatorFactoryInterface
|
||||||
{
|
{
|
||||||
final public const DEFAULT_CURRENT_PAGE_KEY = 'page';
|
final public const DEFAULT_CURRENT_PAGE_KEY = 'page';
|
||||||
|
|
||||||
@@ -29,16 +29,16 @@ final readonly class PaginatorFactory implements PaginatorFactoryInterface
|
|||||||
/**
|
/**
|
||||||
* the request stack.
|
* the request stack.
|
||||||
*/
|
*/
|
||||||
private RequestStack $requestStack,
|
private readonly RequestStack $requestStack,
|
||||||
/**
|
/**
|
||||||
* the router and generator for url.
|
* the router and generator for url.
|
||||||
*/
|
*/
|
||||||
private RouterInterface $router,
|
private readonly RouterInterface $router,
|
||||||
/**
|
/**
|
||||||
* the default item per page. This may be overriden by
|
* the default item per page. This may be overriden by
|
||||||
* the request or inside the paginator.
|
* the request or inside the paginator.
|
||||||
*/
|
*/
|
||||||
private int $itemPerPage = 20,
|
private readonly int $itemPerPage = 20,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -80,8 +80,6 @@ export default {
|
|||||||
return appMessages.fr.the_evaluation_document;
|
return appMessages.fr.the_evaluation_document;
|
||||||
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
|
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
|
||||||
return appMessages.fr.the_workflow;
|
return appMessages.fr.the_workflow;
|
||||||
case "Chill\\TaskBundle\\Entity\\SingleTask":
|
|
||||||
return appMessages.fr.the_task;
|
|
||||||
default:
|
default:
|
||||||
throw "notification type unknown";
|
throw "notification type unknown";
|
||||||
}
|
}
|
||||||
@@ -98,8 +96,6 @@ export default {
|
|||||||
return `/fr/person/accompanying-period/work/evaluation/document/${n.relatedEntityId}/show`;
|
return `/fr/person/accompanying-period/work/evaluation/document/${n.relatedEntityId}/show`;
|
||||||
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
|
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
|
||||||
return `/fr/main/workflow/${n.relatedEntityId}/show`;
|
return `/fr/main/workflow/${n.relatedEntityId}/show`;
|
||||||
case "Chill\\TaskBundle\\Entity\\SingleTask":
|
|
||||||
return `/fr/task/single-task/${n.relatedEntityId}/show`;
|
|
||||||
default:
|
default:
|
||||||
throw "notification type unknown";
|
throw "notification type unknown";
|
||||||
}
|
}
|
||||||
|
@@ -37,7 +37,7 @@ class NotificationNormalizer implements NormalizerAwareInterface, NormalizerInte
|
|||||||
->find($object->getRelatedEntityId());
|
->find($object->getRelatedEntityId());
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => $object->getType(),
|
'type' => 'notification',
|
||||||
'id' => $object->getId(),
|
'id' => $object->getId(),
|
||||||
'addressees' => $this->normalizer->normalize($object->getAddressees(), $format, $context),
|
'addressees' => $this->normalizer->normalize($object->getAddressees(), $format, $context),
|
||||||
'date' => $this->normalizer->normalize($object->getDate(), $format, $context),
|
'date' => $this->normalizer->normalize($object->getDate(), $format, $context),
|
||||||
|
@@ -13,6 +13,7 @@ namespace Chill\TaskBundle\Controller;
|
|||||||
|
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
|
||||||
use Chill\MainBundle\Serializer\Model\Collection;
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
use Chill\MainBundle\Serializer\Model\Counter;
|
use Chill\MainBundle\Serializer\Model\Counter;
|
||||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||||
@@ -22,7 +23,6 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
|||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
||||||
use Chill\TaskBundle\Entity\SingleTask;
|
use Chill\TaskBundle\Entity\SingleTask;
|
||||||
use Chill\TaskBundle\Event\AssignTaskEvent;
|
|
||||||
use Chill\TaskBundle\Event\TaskEvent;
|
use Chill\TaskBundle\Event\TaskEvent;
|
||||||
use Chill\TaskBundle\Event\UI\UIEvent;
|
use Chill\TaskBundle\Event\UI\UIEvent;
|
||||||
use Chill\TaskBundle\Form\SingleTaskType;
|
use Chill\TaskBundle\Form\SingleTaskType;
|
||||||
@@ -48,6 +48,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||||||
final class SingleTaskController extends AbstractController
|
final class SingleTaskController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
private readonly CenterResolverDispatcherInterface $centerResolverDispatcher,
|
||||||
private readonly PaginatorFactory $paginatorFactory,
|
private readonly PaginatorFactory $paginatorFactory,
|
||||||
private readonly SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository,
|
private readonly SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository,
|
||||||
private readonly TranslatorInterface $translator,
|
private readonly TranslatorInterface $translator,
|
||||||
@@ -168,9 +169,6 @@ final class SingleTaskController extends AbstractController
|
|||||||
->setForm($this->setCreateForm($task, TaskVoter::UPDATE));
|
->setForm($this->setCreateForm($task, TaskVoter::UPDATE));
|
||||||
$this->eventDispatcher->dispatch($event, UIEvent::EDIT_FORM);
|
$this->eventDispatcher->dispatch($event, UIEvent::EDIT_FORM);
|
||||||
|
|
||||||
// To keep track of specific assignee change
|
|
||||||
$initialAssignee = $task->getAssignee();
|
|
||||||
|
|
||||||
$form = $event->getForm();
|
$form = $event->getForm();
|
||||||
|
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
@@ -180,13 +178,6 @@ final class SingleTaskController extends AbstractController
|
|||||||
$em = $this->managerRegistry->getManager();
|
$em = $this->managerRegistry->getManager();
|
||||||
$em->persist($task);
|
$em->persist($task);
|
||||||
|
|
||||||
if ($initialAssignee !== $task->getAssignee()) {
|
|
||||||
$this->eventDispatcher->dispatch(
|
|
||||||
new AssignTaskEvent($task, $initialAssignee),
|
|
||||||
AssignTaskEvent::PERSIST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$this->addFlash('success', $this->translator
|
$this->addFlash('success', $this->translator
|
||||||
@@ -534,13 +525,6 @@ final class SingleTaskController extends AbstractController
|
|||||||
|
|
||||||
$this->eventDispatcher->dispatch(new TaskEvent($task), TaskEvent::PERSIST);
|
$this->eventDispatcher->dispatch(new TaskEvent($task), TaskEvent::PERSIST);
|
||||||
|
|
||||||
if (null !== $task->getAssignee()) {
|
|
||||||
$this->eventDispatcher->dispatch(
|
|
||||||
new AssignTaskEvent($task, null),
|
|
||||||
AssignTaskEvent::PERSIST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$this->addFlash('success', $this->translator->trans('The task is created'));
|
$this->addFlash('success', $this->translator->trans('The task is created'));
|
||||||
|
@@ -42,7 +42,6 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface
|
|||||||
$loader->load('services/timeline.yaml');
|
$loader->load('services/timeline.yaml');
|
||||||
$loader->load('services/fixtures.yaml');
|
$loader->load('services/fixtures.yaml');
|
||||||
$loader->load('services/form.yaml');
|
$loader->load('services/form.yaml');
|
||||||
$loader->load('services/notification.yaml');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function prepend(ContainerBuilder $container)
|
public function prepend(ContainerBuilder $container)
|
||||||
|
@@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\TaskBundle\Event;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\User;
|
|
||||||
use Chill\TaskBundle\Entity\SingleTask;
|
|
||||||
use Symfony\Contracts\EventDispatcher\Event;
|
|
||||||
|
|
||||||
class AssignTaskEvent extends Event
|
|
||||||
{
|
|
||||||
final public const PERSIST = 'chill_task.assign_task';
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
private readonly SingleTask $task,
|
|
||||||
private readonly ?User $initialAssignee,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function getTask(): SingleTask
|
|
||||||
{
|
|
||||||
return $this->task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInitialAssignee(): ?User
|
|
||||||
{
|
|
||||||
return $this->initialAssignee;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasAssigneeChanged(): bool
|
|
||||||
{
|
|
||||||
return $this->initialAssignee !== $this->task->getAssignee();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,66 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\TaskBundle\Event;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\Notification;
|
|
||||||
use Chill\TaskBundle\Entity\SingleTask;
|
|
||||||
use Chill\TaskBundle\Notification\AssignTaskNotificationFlagProvider;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
||||||
|
|
||||||
readonly class TaskAssignEventSubscriber implements EventSubscriberInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private EntityManagerInterface $entityManager,
|
|
||||||
private \Twig\Environment $engine,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public static function getSubscribedEvents(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
AssignTaskEvent::PERSIST => ['onTaskAssigned', 0],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a notification when a user is assigned to a task.
|
|
||||||
* Only triggers when the assignee actually changes.
|
|
||||||
*/
|
|
||||||
public function onTaskAssigned(AssignTaskEvent $event): void
|
|
||||||
{
|
|
||||||
if (!$event->hasAssigneeChanged()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$task = $event->getTask();
|
|
||||||
$assignedUser = $task->getAssignee();
|
|
||||||
|
|
||||||
$title = $task->getTitle();
|
|
||||||
|
|
||||||
$context = [
|
|
||||||
'task' => $task,
|
|
||||||
'assignedUser' => $assignedUser,
|
|
||||||
'title' => $title,
|
|
||||||
];
|
|
||||||
|
|
||||||
$notification = new Notification();
|
|
||||||
$notification
|
|
||||||
->setRelatedEntityId($task->getId())
|
|
||||||
->setRelatedEntityClass(SingleTask::class)
|
|
||||||
->setTitle($this->engine->render('@ChillTask/Notification/task_assignment_notification_title.txt.twig', $context))
|
|
||||||
->setMessage($this->engine->render('@ChillTask/Notification/task_assignment_notification_content.txt.twig', $context))
|
|
||||||
->addAddressee($assignedUser)
|
|
||||||
->setType(AssignTaskNotificationFlagProvider::FLAG);
|
|
||||||
|
|
||||||
$this->entityManager->persist($notification);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\TaskBundle\Notification;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Notification\FlagProviders\NotificationFlagProviderInterface;
|
|
||||||
use Symfony\Component\Translation\TranslatableMessage;
|
|
||||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
|
||||||
|
|
||||||
class AssignTaskNotificationFlagProvider implements NotificationFlagProviderInterface
|
|
||||||
{
|
|
||||||
public const FLAG = 'task-assign-notif';
|
|
||||||
|
|
||||||
public function getFlag(): string
|
|
||||||
{
|
|
||||||
return self::FLAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLabel(): TranslatableInterface
|
|
||||||
{
|
|
||||||
return new TranslatableMessage('notification.flags.task_assign');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,69 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\TaskBundle\Notification;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\Notification;
|
|
||||||
use Chill\MainBundle\Notification\NotificationHandlerInterface;
|
|
||||||
use Chill\TaskBundle\Entity\SingleTask;
|
|
||||||
use Chill\TaskBundle\Repository\SingleTaskRepository;
|
|
||||||
use Symfony\Component\Translation\TranslatableMessage;
|
|
||||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
|
||||||
|
|
||||||
final readonly class TaskNotificationHandler implements NotificationHandlerInterface
|
|
||||||
{
|
|
||||||
public function __construct(private SingleTaskRepository $taskRepository) {}
|
|
||||||
|
|
||||||
public function getTemplate(Notification $notification, array $options = []): string
|
|
||||||
{
|
|
||||||
return '@ChillTask/SingleTask/showInNotification.html.twig';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTemplateData(Notification $notification, array $options = []): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'notification' => $notification,
|
|
||||||
'task' => $this->taskRepository->find($notification->getRelatedEntityId()),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supports(Notification $notification, array $options = []): bool
|
|
||||||
{
|
|
||||||
return SingleTask::class === $notification->getRelatedEntityClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTitle(Notification $notification, array $options = []): TranslatableInterface
|
|
||||||
{
|
|
||||||
if (null === $task = $this->getRelatedEntity($notification)) {
|
|
||||||
return new TranslatableMessage('task.deleted');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TranslatableMessage('notification.task.title %title%', ['title' => $task->getTitle()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAssociatedPersons(Notification $notification, array $options = []): array
|
|
||||||
{
|
|
||||||
if (null === $task = $this->getRelatedEntity($notification)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $task->getCourse()) {
|
|
||||||
return $task->getCourse()->getParticipations()->getValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [$task->getPerson()];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRelatedEntity(Notification $notification): ?object
|
|
||||||
{
|
|
||||||
return $this->taskRepository->find($notification->getRelatedEntityId());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
{{ assignedUser.label }},
|
|
||||||
|
|
||||||
{{ 'notification.email.task_assigned'|trans({}, null, assignedUser.getLocale) }}
|
|
||||||
|
|
||||||
{{ 'notification.email.title_label'|trans({}, null, assignedUser.getLocale) }} "{{ task.title }}".
|
|
||||||
{% if task.endDate %}
|
|
||||||
|
|
||||||
{{ 'notification.email.deadline'|trans({'%date%': task.endDate|format_date('long')}, null, assignedUser.getLocale) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{{ 'notification.email.view_task'|trans({}, null, assignedUser.getLocale) }}
|
|
||||||
|
|
||||||
{{ absolute_url(path('chill_task_single_task_show', {'id': task.id, '_locale': assignedUser.getLocale})) }}
|
|
||||||
|
|
||||||
{{ 'notification.email.regards'|trans({}, null, assignedUser.getLocale) }},
|
|
@@ -1,3 +0,0 @@
|
|||||||
{{ 'notification.email.title'|trans({}, null, assignedUser.getLocale) }}
|
|
||||||
|
|
||||||
|
|
@@ -18,14 +18,14 @@
|
|||||||
<div>
|
<div>
|
||||||
{% if task.person is not null %}
|
{% if task.person is not null %}
|
||||||
<span class="chill-task-list__row__person">
|
<span class="chill-task-list__row__person">
|
||||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||||
targetEntity: { name: 'person', id: task.person.id },
|
targetEntity: { name: 'person', id: task.person.id },
|
||||||
action: 'show',
|
action: 'show',
|
||||||
displayBadge: true,
|
displayBadge: true,
|
||||||
buttonText: task.person|chill_entity_render_string,
|
buttonText: task.person|chill_entity_render_string,
|
||||||
isDead: task.person.deathdate is not null
|
isDead: task.person.deathdate is not null
|
||||||
} %}
|
} %}
|
||||||
</span>
|
</span>
|
||||||
{% elseif task.course is not null %}
|
{% elseif task.course is not null %}
|
||||||
<div style="margin-bottom: 1rem;">
|
<div style="margin-bottom: 1rem;">
|
||||||
{% for part in task.course.currentParticipations %}
|
{% for part in task.course.currentParticipations %}
|
||||||
|
@@ -110,5 +110,4 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</ul>
|
</ul></div>
|
||||||
</div>
|
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
{% macro recordAction(task) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('chill_person_accompanying_course_index', { 'task_id': task }) }}"
|
|
||||||
class="btn btn-show" title="{{ 'See task'|trans }}"></a>
|
|
||||||
</li>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% if task is not null %}
|
|
||||||
{# <div>Todo : display task? </div>#}
|
|
||||||
{% else %}
|
|
||||||
<div class="alert alert-warning border-warning border-1">
|
|
||||||
{{ 'You are getting a notification for a task which does not exist any more'|trans }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
@@ -1,138 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\TaskBundle\Tests\EventSubscriber;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\Notification;
|
|
||||||
use Chill\MainBundle\Entity\User;
|
|
||||||
use Chill\TaskBundle\Entity\SingleTask;
|
|
||||||
use Chill\TaskBundle\Event\AssignTaskEvent;
|
|
||||||
use Chill\TaskBundle\Event\TaskAssignEventSubscriber;
|
|
||||||
use Chill\TaskBundle\Notification\AssignTaskNotificationFlagProvider;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Prophecy\Argument;
|
|
||||||
use Prophecy\PhpUnit\ProphecyTrait;
|
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
|
||||||
use Twig\Environment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class TaskAssignEventSubscriberTest extends TestCase
|
|
||||||
{
|
|
||||||
use ProphecyTrait;
|
|
||||||
|
|
||||||
private ObjectProphecy $entityManager;
|
|
||||||
private ObjectProphecy $twig;
|
|
||||||
private TaskAssignEventSubscriber $subscriber;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
$this->entityManager = $this->prophesize(EntityManagerInterface::class);
|
|
||||||
$this->twig = $this->prophesize(Environment::class);
|
|
||||||
$this->subscriber = new TaskAssignEventSubscriber(
|
|
||||||
$this->entityManager->reveal(),
|
|
||||||
$this->twig->reveal()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setEntityId(object $entity, int $id): void
|
|
||||||
{
|
|
||||||
$reflection = new \ReflectionClass($entity);
|
|
||||||
$property = $reflection->getProperty('id');
|
|
||||||
$property->setAccessible(true);
|
|
||||||
$property->setValue($entity, $id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testOnTaskAssignedCreatesNotificationWhenAssigneeChanges(): void
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
$initialAssignee = new User();
|
|
||||||
$newAssignee = new User();
|
|
||||||
|
|
||||||
$task = new SingleTask();
|
|
||||||
$task->setTitle('Test Task');
|
|
||||||
$task->setAssignee($newAssignee);
|
|
||||||
$this->setEntityId($task, 123);
|
|
||||||
|
|
||||||
$event = new AssignTaskEvent($task, $initialAssignee);
|
|
||||||
|
|
||||||
$this->twig->render('@ChillTask/Notification/task_assignment_notification_title.txt.twig', Argument::type('array'))
|
|
||||||
->shouldBeCalledOnce()
|
|
||||||
->willReturn('Notification Title');
|
|
||||||
|
|
||||||
$this->twig->render('@ChillTask/Notification/task_assignment_notification_content.txt.twig', Argument::type('array'))
|
|
||||||
->shouldBeCalledOnce()
|
|
||||||
->willReturn('Notification Content');
|
|
||||||
|
|
||||||
$this->entityManager->persist(Argument::type(Notification::class))
|
|
||||||
->shouldBeCalledOnce();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$this->subscriber->onTaskAssigned($event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testOnTaskAssignedDoesNothingWhenAssigneeDoesNotChange(): void
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
$assignee = new User();
|
|
||||||
|
|
||||||
$task = new SingleTask();
|
|
||||||
$task->setTitle('Test Task');
|
|
||||||
$task->setAssignee($assignee);
|
|
||||||
|
|
||||||
$event = new AssignTaskEvent($task, $assignee);
|
|
||||||
|
|
||||||
$this->twig->render(Argument::any(), Argument::any())->shouldNotBeCalled();
|
|
||||||
$this->entityManager->persist(Argument::any())->shouldNotBeCalled();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$this->subscriber->onTaskAssigned($event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testNotificationHasCorrectProperties(): void
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
$initialAssignee = new User();
|
|
||||||
$newAssignee = new User();
|
|
||||||
|
|
||||||
$task = new SingleTask();
|
|
||||||
$task->setTitle('Important Task');
|
|
||||||
$task->setAssignee($newAssignee);
|
|
||||||
$this->setEntityId($task, 456);
|
|
||||||
|
|
||||||
$event = new AssignTaskEvent($task, $initialAssignee);
|
|
||||||
|
|
||||||
$this->twig->render(Argument::any(), Argument::any())->willReturn('Test Content');
|
|
||||||
|
|
||||||
// Capture the persisted notification
|
|
||||||
$persistedNotification = null;
|
|
||||||
$this->entityManager->persist(Argument::type(Notification::class))
|
|
||||||
->shouldBeCalledOnce()
|
|
||||||
->will(function ($args) use (&$persistedNotification) {
|
|
||||||
$persistedNotification = $args[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$this->subscriber->onTaskAssigned($event);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
$this->assertInstanceOf(Notification::class, $persistedNotification);
|
|
||||||
$this->assertEquals($task->getId(), $persistedNotification->getRelatedEntityId());
|
|
||||||
$this->assertEquals(SingleTask::class, $persistedNotification->getRelatedEntityClass());
|
|
||||||
$this->assertEquals(AssignTaskNotificationFlagProvider::FLAG, $persistedNotification->getType());
|
|
||||||
$this->assertEquals('Test Content', $persistedNotification->getTitle());
|
|
||||||
$this->assertEquals('Test Content', $persistedNotification->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
_defaults:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
|
|
||||||
Chill\TaskBundle\Event\Lifecycle\TaskLifecycleEvent:
|
Chill\TaskBundle\Event\Lifecycle\TaskLifecycleEvent:
|
||||||
arguments:
|
arguments:
|
||||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||||
tags:
|
tags:
|
||||||
- { name: kernel.event_subscriber }
|
- { name: kernel.event_subscriber }
|
||||||
|
|
||||||
Chill\TaskBundle\Event\TaskAssignEventSubscriber: ~
|
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
services:
|
|
||||||
_defaults:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
|
|
||||||
Chill\TaskBundle\Notification\TaskNotificationHandler: ~
|
|
||||||
Chill\TaskBundle\Notification\AssignTaskNotificationFlagProvider: ~
|
|
@@ -116,16 +116,3 @@ CHILL_TASK_TASK_UPDATE: Modifier une tâche
|
|||||||
CHILL_TASK_TASK_CREATE_FOR_COURSE: Créer une tâche pour un parcours
|
CHILL_TASK_TASK_CREATE_FOR_COURSE: Créer une tâche pour un parcours
|
||||||
CHILL_TASK_TASK_CREATE_FOR_PERSON: Créer une tâche pour un usager
|
CHILL_TASK_TASK_CREATE_FOR_PERSON: Créer une tâche pour un usager
|
||||||
|
|
||||||
notification:
|
|
||||||
task:
|
|
||||||
title %title%: "Tâche: title"
|
|
||||||
flags:
|
|
||||||
task_assign: Lorsqu'un autre utilisateur m'assigne à une tâche.
|
|
||||||
email:
|
|
||||||
title: "Une tâche demande votre attention"
|
|
||||||
task_assigned: "Une tâche vous a été assignée."
|
|
||||||
title_label: "Titre de la tâche:"
|
|
||||||
deadline: "Vous êtes invités à accomplir cette tâche avant le %date%"
|
|
||||||
view_task: "Vous pouvez visualiser la tâche sur cette page:"
|
|
||||||
regards: "Cordialement"
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user