mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-12 21:34:25 +00:00
Merge branch 'calendar/docgen-add-generation' into 'calendar/finalization'
[Calendar] Generate document associate with calendar See merge request Chill-Projet/chill-bundles!462
This commit is contained in:
commit
823d27ed8a
@ -16,6 +16,8 @@ and this project adheres to
|
|||||||
* [activity][export] DX/Feature: use of an `ActivityTypeRepositoryInterface` instead of the old-style EntityRepository
|
* [activity][export] DX/Feature: use of an `ActivityTypeRepositoryInterface` instead of the old-style EntityRepository
|
||||||
* [person][export] Fixed: some inconsistency with date filter on accompanying courses
|
* [person][export] Fixed: some inconsistency with date filter on accompanying courses
|
||||||
* [person][export] Fixed: use left join for related entities in accompanying course aggregators
|
* [person][export] Fixed: use left join for related entities in accompanying course aggregators
|
||||||
|
* [workflow] Feature: allow user to copy and send manually the access link for the workflow
|
||||||
|
* [workflow] Feature: show the email addresses that received an access link for the workflow
|
||||||
|
|
||||||
## Test releases
|
## Test releases
|
||||||
|
|
||||||
@ -32,6 +34,7 @@ and this project adheres to
|
|||||||
* [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty
|
* [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty
|
||||||
* [search]: Order of birthdate fields changed in advanced search to avoid confusion.
|
* [search]: Order of birthdate fields changed in advanced search to avoid confusion.
|
||||||
* [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675)
|
* [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675)
|
||||||
|
* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625)
|
||||||
* [household]: Reposition and cut button for enfant hors menage have been deleted (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/620)
|
* [household]: Reposition and cut button for enfant hors menage have been deleted (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/620)
|
||||||
* [admin]: Add crud for composition type in admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/611)
|
* [admin]: Add crud for composition type in admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/611)
|
||||||
|
|
||||||
|
@ -175,13 +175,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
|
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
|
||||||
|
|
||||||
-
|
|
||||||
message: """
|
|
||||||
#^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:
|
|
||||||
Use getReachableCircles$#
|
|
||||||
"""
|
|
||||||
count: 1
|
|
||||||
path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: """
|
message: """
|
||||||
@ -294,14 +287,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillEventBundle/Form/Type/PickEventType.php
|
path: src/Bundle/ChillEventBundle/Form/Type/PickEventType.php
|
||||||
|
|
||||||
-
|
|
||||||
message: """
|
|
||||||
#^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:
|
|
||||||
Use getReachableCircles$#
|
|
||||||
"""
|
|
||||||
count: 1
|
|
||||||
path: src/Bundle/ChillEventBundle/Search/EventSearch.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: """
|
message: """
|
||||||
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
||||||
@ -390,13 +375,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillMainBundle/Export/ExportInterface.php
|
path: src/Bundle/ChillMainBundle/Export/ExportInterface.php
|
||||||
|
|
||||||
-
|
|
||||||
message: """
|
|
||||||
#^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:
|
|
||||||
Use getReachableCircles$#
|
|
||||||
"""
|
|
||||||
count: 1
|
|
||||||
path: src/Bundle/ChillMainBundle/Export/ExportManager.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: """
|
message: """
|
||||||
@ -754,14 +732,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php
|
path: src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php
|
||||||
|
|
||||||
-
|
|
||||||
message: """
|
|
||||||
#^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:
|
|
||||||
Use getReachableCircles$#
|
|
||||||
"""
|
|
||||||
count: 1
|
|
||||||
path: src/Bundle/ChillReportBundle/Controller/ReportController.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: """
|
message: """
|
||||||
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
||||||
@ -794,14 +764,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php
|
path: src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php
|
||||||
|
|
||||||
-
|
|
||||||
message: """
|
|
||||||
#^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:
|
|
||||||
Use getReachableCircles$#
|
|
||||||
"""
|
|
||||||
count: 1
|
|
||||||
path: src/Bundle/ChillReportBundle/Form/ReportType.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: """
|
message: """
|
||||||
#^Parameter \\$role of method Chill\\\\ReportBundle\\\\Form\\\\ReportType\\:\\:appendScopeChoices\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
#^Parameter \\$role of method Chill\\\\ReportBundle\\\\Form\\\\ReportType\\:\\:appendScopeChoices\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
||||||
@ -810,14 +772,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillReportBundle/Form/ReportType.php
|
path: src/Bundle/ChillReportBundle/Form/ReportType.php
|
||||||
|
|
||||||
-
|
|
||||||
message: """
|
|
||||||
#^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:
|
|
||||||
Use getReachableCircles$#
|
|
||||||
"""
|
|
||||||
count: 1
|
|
||||||
path: src/Bundle/ChillReportBundle/Search/ReportSearch.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: """
|
message: """
|
||||||
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
||||||
@ -826,13 +780,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillReportBundle/Search/ReportSearch.php
|
path: src/Bundle/ChillReportBundle/Search/ReportSearch.php
|
||||||
|
|
||||||
-
|
|
||||||
message: """
|
|
||||||
#^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:
|
|
||||||
Use getReachableCircles$#
|
|
||||||
"""
|
|
||||||
count: 2
|
|
||||||
path: src/Bundle/ChillReportBundle/Timeline/TimelineReportProvider.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: """
|
message: """
|
||||||
|
@ -15,7 +15,7 @@ use Chill\CalendarBundle\Entity\Calendar;
|
|||||||
use Chill\CalendarBundle\Form\CalendarType;
|
use Chill\CalendarBundle\Form\CalendarType;
|
||||||
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
|
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
|
||||||
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
|
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
|
||||||
use Chill\CalendarBundle\Repository\CalendarRepository;
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Repository\UserRepository;
|
use Chill\MainBundle\Repository\UserRepository;
|
||||||
@ -42,7 +42,7 @@ class CalendarController extends AbstractController
|
|||||||
{
|
{
|
||||||
private CalendarACLAwareRepositoryInterface $calendarACLAwareRepository;
|
private CalendarACLAwareRepositoryInterface $calendarACLAwareRepository;
|
||||||
|
|
||||||
private CalendarRepository $calendarRepository;
|
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||||
|
|
||||||
private FilterOrderHelperFactoryInterface $filterOrderHelperFactory;
|
private FilterOrderHelperFactoryInterface $filterOrderHelperFactory;
|
||||||
|
|
||||||
@ -57,8 +57,8 @@ class CalendarController extends AbstractController
|
|||||||
private UserRepository $userRepository;
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
CalendarRepository $calendarRepository,
|
|
||||||
CalendarACLAwareRepositoryInterface $calendarACLAwareRepository,
|
CalendarACLAwareRepositoryInterface $calendarACLAwareRepository,
|
||||||
|
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||||
FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
|
FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
PaginatorFactory $paginator,
|
PaginatorFactory $paginator,
|
||||||
@ -66,8 +66,8 @@ class CalendarController extends AbstractController
|
|||||||
SerializerInterface $serializer,
|
SerializerInterface $serializer,
|
||||||
UserRepository $userRepository
|
UserRepository $userRepository
|
||||||
) {
|
) {
|
||||||
$this->calendarRepository = $calendarRepository;
|
|
||||||
$this->calendarACLAwareRepository = $calendarACLAwareRepository;
|
$this->calendarACLAwareRepository = $calendarACLAwareRepository;
|
||||||
|
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||||
$this->filterOrderHelperFactory = $filterOrderHelperFactory;
|
$this->filterOrderHelperFactory = $filterOrderHelperFactory;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->paginator = $paginator;
|
$this->paginator = $paginator;
|
||||||
@ -152,7 +152,10 @@ class CalendarController extends AbstractController
|
|||||||
$view = '@ChillCalendar/Calendar/editByUser.html.twig';
|
$view = '@ChillCalendar/Calendar/editByUser.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = $this->createForm(CalendarType::class, $entity);
|
$form = $this->createForm(CalendarType::class, $entity)
|
||||||
|
->add('save', SubmitType::class)
|
||||||
|
->add('save_and_create_doc', SubmitType::class);
|
||||||
|
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
@ -162,6 +165,10 @@ class CalendarController extends AbstractController
|
|||||||
|
|
||||||
$params = $this->buildParamsToUrl($user, $accompanyingPeriod);
|
$params = $this->buildParamsToUrl($user, $accompanyingPeriod);
|
||||||
|
|
||||||
|
if ($form->get('save_and_create_doc')->isClicked()) {
|
||||||
|
return $this->redirectToRoute('chill_calendar_calendardoc_pick_template', ['id' => $entity->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->redirectToRoute('chill_calendar_calendar_list_by_period', $params);
|
return $this->redirectToRoute('chill_calendar_calendar_list_by_period', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,6 +221,7 @@ class CalendarController extends AbstractController
|
|||||||
'accompanyingCourse' => $accompanyingPeriod,
|
'accompanyingCourse' => $accompanyingPeriod,
|
||||||
'paginator' => $paginator,
|
'paginator' => $paginator,
|
||||||
'filterOrder' => $filterOrder,
|
'filterOrder' => $filterOrder,
|
||||||
|
'hasDocs' => 0 < $this->docGeneratorTemplateRepository->countByEntity(Calendar::class),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +284,10 @@ class CalendarController extends AbstractController
|
|||||||
$entity->setAccompanyingPeriod($accompanyingPeriod);
|
$entity->setAccompanyingPeriod($accompanyingPeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = $this->createForm(CalendarType::class, $entity);
|
$form = $this->createForm(CalendarType::class, $entity)
|
||||||
|
->add('save', SubmitType::class)
|
||||||
|
->add('save_and_create_doc', SubmitType::class);
|
||||||
|
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
@ -287,6 +298,10 @@ class CalendarController extends AbstractController
|
|||||||
|
|
||||||
$params = $this->buildParamsToUrl($user, $accompanyingPeriod);
|
$params = $this->buildParamsToUrl($user, $accompanyingPeriod);
|
||||||
|
|
||||||
|
if ($form->get('save_and_create_doc')->isClicked()) {
|
||||||
|
return $this->redirectToRoute('chill_calendar_calendardoc_pick_template', ['id' => $entity->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->redirectToRoute('chill_calendar_calendar_list_by_period', $params);
|
return $this->redirectToRoute('chill_calendar_calendar_list_by_period', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
<?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\Security\Voter\CalendarVoter;
|
||||||
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
use Symfony\Component\Templating\EngineInterface;
|
||||||
|
|
||||||
|
class CalendarDocController
|
||||||
|
{
|
||||||
|
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||||
|
|
||||||
|
private EngineInterface $engine;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
private SerializerInterface $serializer;
|
||||||
|
|
||||||
|
private UrlGeneratorInterface $urlGenerator;
|
||||||
|
|
||||||
|
public function __construct(Security $security, DocGeneratorTemplateRepository $docGeneratorTemplateRepository, UrlGeneratorInterface $urlGenerator, EngineInterface $engine)
|
||||||
|
{
|
||||||
|
$this->security = $security;
|
||||||
|
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->engine = $engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{_locale}/calendar/docgen/pick/{id}", name="chill_calendar_calendardoc_pick_template")
|
||||||
|
*/
|
||||||
|
public function pickTemplate(Calendar $calendar): Response
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted(CalendarVoter::SEE, $calendar)) {
|
||||||
|
throw new AccessDeniedException('Not authorized to see this calendar');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $number = $this->docGeneratorTemplateRepository->countByEntity(Calendar::class)) {
|
||||||
|
throw new RuntimeException('should not be redirected to this page if no template');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 === $number) {
|
||||||
|
$templates = $this->docGeneratorTemplateRepository->findByEntity(Calendar::class);
|
||||||
|
|
||||||
|
return new RedirectResponse(
|
||||||
|
$this->urlGenerator->generate(
|
||||||
|
'chill_docgenerator_generate_from_template',
|
||||||
|
[
|
||||||
|
'template' => $templates[0]->getId(),
|
||||||
|
'entityClassName' => Calendar::class,
|
||||||
|
'entityId' => $calendar->getId(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
$this->engine->render('@ChillCalendar/CalendarDoc/pick_template.html.twig', [
|
||||||
|
'calendar' => $calendar,
|
||||||
|
'accompanyingCourse' => $calendar->getAccompanyingPeriod(),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,9 @@ use function in_array;
|
|||||||
* uniqueConstraints={@ORM\UniqueConstraint(name="idx_calendar_remote", columns={"remoteId"}, options={"where": "remoteId <> ''"})}
|
* uniqueConstraints={@ORM\UniqueConstraint(name="idx_calendar_remote", columns={"remoteId"}, options={"where": "remoteId <> ''"})}
|
||||||
* )
|
* )
|
||||||
* @ORM\Entity
|
* @ORM\Entity
|
||||||
|
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||||
|
* "chill_calendar_calendar": Calendar::class
|
||||||
|
* })
|
||||||
*/
|
*/
|
||||||
class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
||||||
{
|
{
|
||||||
@ -109,13 +112,24 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_")
|
* @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_")
|
||||||
* @Serializer\Groups({"calendar:read", "read"})
|
* @Serializer\Groups({"calendar:read", "read", "docgen:read"})
|
||||||
*/
|
*/
|
||||||
private CommentEmbeddable $comment;
|
private CommentEmbeddable $comment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="integer", nullable=false, options={"default": 0})
|
||||||
|
*/
|
||||||
|
private int $dateTimeVersion = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<CalendarDoc::class>
|
||||||
|
* @ORM\OneToMany(targetEntity=CalendarDoc::class, mappedBy="calendar", orphanRemoval=true)
|
||||||
|
*/
|
||||||
|
private Collection $documents;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="datetime_immutable", nullable=false)
|
* @ORM\Column(type="datetime_immutable", nullable=false)
|
||||||
* @Serializer\Groups({"calendar:read", "read", "calendar:light"})
|
* @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"})
|
||||||
* @Assert\NotNull(message="calendar.An end date is required")
|
* @Assert\NotNull(message="calendar.An end date is required")
|
||||||
*/
|
*/
|
||||||
private ?DateTimeImmutable $endDate = null;
|
private ?DateTimeImmutable $endDate = null;
|
||||||
@ -124,7 +138,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
* @ORM\GeneratedValue
|
* @ORM\GeneratedValue
|
||||||
* @ORM\Column(type="integer")
|
* @ORM\Column(type="integer")
|
||||||
* @Serializer\Groups({"calendar:read", "read", "calendar:light"})
|
* @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"})
|
||||||
*/
|
*/
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
@ -136,20 +150,20 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* cascade={"persist", "remove", "merge", "detach"}
|
* cascade={"persist", "remove", "merge", "detach"}
|
||||||
* )
|
* )
|
||||||
* @ORM\JoinTable(name="chill_calendar.calendar_to_invites")
|
* @ORM\JoinTable(name="chill_calendar.calendar_to_invites")
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "docgen:read"})
|
||||||
*/
|
*/
|
||||||
private Collection $invites;
|
private Collection $invites;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
|
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "docgen:read"})
|
||||||
* @Assert\NotNull(message="calendar.A location is required")
|
* @Assert\NotNull(message="calendar.A location is required")
|
||||||
*/
|
*/
|
||||||
private ?Location $location = null;
|
private ?Location $location = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
|
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
|
||||||
* @Serializer\Groups({"calendar:read", "read", "calendar:light"})
|
* @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"})
|
||||||
* @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"})
|
* @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"})
|
||||||
* @Assert\NotNull(message="calendar.A main user is mandatory")
|
* @Assert\NotNull(message="calendar.A main user is mandatory")
|
||||||
*/
|
*/
|
||||||
@ -158,7 +172,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
/**
|
/**
|
||||||
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person", inversedBy="calendars")
|
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person", inversedBy="calendars")
|
||||||
* @ORM\JoinTable(name="chill_calendar.calendar_to_persons")
|
* @ORM\JoinTable(name="chill_calendar.calendar_to_persons")
|
||||||
* @Serializer\Groups({"calendar:read", "read", "calendar:light"})
|
* @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"})
|
||||||
* @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"})
|
* @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"})
|
||||||
* @Assert\Count(min=1, minMessage="calendar.At least {{ limit }} person is required.")
|
* @Assert\Count(min=1, minMessage="calendar.At least {{ limit }} person is required.")
|
||||||
*/
|
*/
|
||||||
@ -173,13 +187,14 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
/**
|
/**
|
||||||
* @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
|
* @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
|
||||||
* @ORM\JoinTable(name="chill_calendar.calendar_to_thirdparties")
|
* @ORM\JoinTable(name="chill_calendar.calendar_to_thirdparties")
|
||||||
* @Serializer\Groups({"calendar:read", "read", "calendar:light"})
|
* @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"})
|
||||||
* @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"})
|
* @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"})
|
||||||
*/
|
*/
|
||||||
private Collection $professionals;
|
private Collection $professionals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="boolean", nullable=true)
|
* @ORM\Column(type="boolean", nullable=true)
|
||||||
|
* @Serializer\Groups({"docgen:read"})
|
||||||
*/
|
*/
|
||||||
private ?bool $sendSMS = false;
|
private ?bool $sendSMS = false;
|
||||||
|
|
||||||
@ -190,7 +205,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="datetime_immutable", nullable=false)
|
* @ORM\Column(type="datetime_immutable", nullable=false)
|
||||||
* @Serializer\Groups({"calendar:read", "read", "calendar:light"})
|
* @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"})
|
||||||
* @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"})
|
* @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"})
|
||||||
* @Assert\NotNull(message="calendar.A start date is required")
|
* @Assert\NotNull(message="calendar.A start date is required")
|
||||||
*/
|
*/
|
||||||
@ -205,18 +220,32 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="boolean", nullable=true)
|
* @ORM\Column(type="boolean", nullable=true)
|
||||||
|
* @Serializer\Groups({"docgen:read"})
|
||||||
*/
|
*/
|
||||||
private ?bool $urgent = false;
|
private ?bool $urgent = false;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->comment = new CommentEmbeddable();
|
$this->comment = new CommentEmbeddable();
|
||||||
|
$this->documents = new ArrayCollection();
|
||||||
$this->privateComment = new PrivateCommentEmbeddable();
|
$this->privateComment = new PrivateCommentEmbeddable();
|
||||||
$this->persons = new ArrayCollection();
|
$this->persons = new ArrayCollection();
|
||||||
$this->professionals = new ArrayCollection();
|
$this->professionals = new ArrayCollection();
|
||||||
$this->invites = new ArrayCollection();
|
$this->invites = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal use @{CalendarDoc::__construct} instead
|
||||||
|
*/
|
||||||
|
public function addDocument(CalendarDoc $calendarDoc): self
|
||||||
|
{
|
||||||
|
if ($this->documents->contains($calendarDoc)) {
|
||||||
|
$this->documents[] = $calendarDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal Use {@link (Calendar::addUser)} instead
|
* @internal Use {@link (Calendar::addUser)} instead
|
||||||
*/
|
*/
|
||||||
@ -282,6 +311,22 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $this->comment;
|
return $this->comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each time the date and time is update, this version is incremented.
|
||||||
|
*/
|
||||||
|
public function getDateTimeVersion(): int
|
||||||
|
{
|
||||||
|
return $this->dateTimeVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDocuments(): Collection
|
||||||
|
{
|
||||||
|
return $this->documents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Serializer\Groups({"docgen:read"})
|
||||||
|
*/
|
||||||
public function getDuration(): ?DateInterval
|
public function getDuration(): ?DateInterval
|
||||||
{
|
{
|
||||||
if ($this->getStartDate() === null || $this->getEndDate() === null) {
|
if ($this->getStartDate() === null || $this->getEndDate() === null) {
|
||||||
@ -464,6 +509,18 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal use @{CalendarDoc::setCalendar} with null instead
|
||||||
|
*/
|
||||||
|
public function removeDocument(CalendarDoc $calendarDoc): self
|
||||||
|
{
|
||||||
|
if ($calendarDoc->getCalendar() !== $this) {
|
||||||
|
throw new LogicException('cannot remove document of another calendar');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal Use {@link (Calendar::removeUser)} instead
|
* @internal Use {@link (Calendar::removeUser)} instead
|
||||||
*/
|
*/
|
||||||
@ -554,6 +611,10 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
public function setEndDate(DateTimeImmutable $endDate): self
|
public function setEndDate(DateTimeImmutable $endDate): self
|
||||||
{
|
{
|
||||||
|
if (null === $this->endDate || $this->endDate->getTimestamp() !== $endDate->getTimestamp()) {
|
||||||
|
$this->increaseaDatetimeVersion();
|
||||||
|
}
|
||||||
|
|
||||||
$this->endDate = $endDate;
|
$this->endDate = $endDate;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -601,6 +662,10 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
public function setStartDate(DateTimeImmutable $startDate): self
|
public function setStartDate(DateTimeImmutable $startDate): self
|
||||||
{
|
{
|
||||||
|
if (null === $this->startDate || $this->startDate->getTimestamp() !== $startDate->getTimestamp()) {
|
||||||
|
$this->increaseaDatetimeVersion();
|
||||||
|
}
|
||||||
|
|
||||||
$this->startDate = $startDate;
|
$this->startDate = $startDate;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -623,4 +688,9 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function increaseaDatetimeVersion(): void
|
||||||
|
{
|
||||||
|
++$this->dateTimeVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
135
src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php
Normal file
135
src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<?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\Entity;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(
|
||||||
|
* name="chill_calendar.calendar_doc",
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class CalendarDoc implements TrackCreationInterface, TrackUpdateInterface
|
||||||
|
{
|
||||||
|
use TrackCreationTrait;
|
||||||
|
|
||||||
|
use TrackUpdateTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=Calendar::class, inversedBy="documents")
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private Calendar $calendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="integer", nullable=false, options={"default": 0})
|
||||||
|
*/
|
||||||
|
private int $datetimeVersion = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=StoredObject::class, cascade={"persist"})
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private StoredObject $storedObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="boolean", nullable=false, options={"default": false})
|
||||||
|
*/
|
||||||
|
private bool $trackDateTimeVersion = false;
|
||||||
|
|
||||||
|
public function __construct(Calendar $calendar, StoredObject $storedObject)
|
||||||
|
{
|
||||||
|
$this->setCalendar($calendar);
|
||||||
|
|
||||||
|
$this->storedObject = $storedObject;
|
||||||
|
$this->datetimeVersion = $calendar->getDateTimeVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCalendar(): Calendar
|
||||||
|
{
|
||||||
|
return $this->calendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDatetimeVersion(): int
|
||||||
|
{
|
||||||
|
return $this->datetimeVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStoredObject(): StoredObject
|
||||||
|
{
|
||||||
|
return $this->storedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isTrackDateTimeVersion(): bool
|
||||||
|
{
|
||||||
|
return $this->trackDateTimeVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal use @see{Calendar::removeDocument} instead
|
||||||
|
*
|
||||||
|
* @param Calendar $calendar
|
||||||
|
*/
|
||||||
|
public function setCalendar(?Calendar $calendar): CalendarDoc
|
||||||
|
{
|
||||||
|
if (null === $calendar) {
|
||||||
|
$this->calendar->removeDocument($this);
|
||||||
|
} else {
|
||||||
|
$calendar->addDocument($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->calendar = $calendar;
|
||||||
|
|
||||||
|
$this->datetimeVersion = $calendar->getDateTimeVersion();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDatetimeVersion(int $datetimeVersion): CalendarDoc
|
||||||
|
{
|
||||||
|
$this->datetimeVersion = $datetimeVersion;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStoredObject(StoredObject $storedObject): CalendarDoc
|
||||||
|
{
|
||||||
|
$this->storedObject = $storedObject;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTrackDateTimeVersion(bool $trackDateTimeVersion): CalendarDoc
|
||||||
|
{
|
||||||
|
$this->trackDateTimeVersion = $trackDateTimeVersion;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -73,14 +73,14 @@ class Invite implements TrackUpdateInterface, TrackCreationInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="text", nullable=false, options={"default": "pending"})
|
* @ORM\Column(type="text", nullable=false, options={"default": "pending"})
|
||||||
* @Serializer\Groups(groups={"calendar:read", "read"})
|
* @Serializer\Groups(groups={"calendar:read", "read", "docgen:read"})
|
||||||
*/
|
*/
|
||||||
private string $status = self::PENDING;
|
private string $status = self::PENDING;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
|
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
|
||||||
* @ORM\JoinColumn(nullable=false)
|
* @ORM\JoinColumn(nullable=false)
|
||||||
* @Serializer\Groups(groups={"calendar:read", "read"})
|
* @Serializer\Groups(groups={"calendar:read", "read", "docgen:read"})
|
||||||
*/
|
*/
|
||||||
private ?User $user = null;
|
private ?User $user = null;
|
||||||
|
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
<?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\Repository;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
class CalendarDocRepository implements ObjectRepository, CalendarDocRepositoryInterface
|
||||||
|
{
|
||||||
|
private EntityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id): ?CalendarDoc
|
||||||
|
{
|
||||||
|
return $this->repository->find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null)
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?CalendarDoc
|
||||||
|
{
|
||||||
|
return $this->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return CalendarDoc::class;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<?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\Repository;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
|
||||||
|
interface CalendarDocRepositoryInterface
|
||||||
|
{
|
||||||
|
public function find($id): ?CalendarDoc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|CalendarDoc[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|CalendarDoc[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null);
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?CalendarDoc;
|
||||||
|
|
||||||
|
public function getClassName();
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
{% if calendar.documents|length > 0 %}
|
||||||
|
|
||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
|
||||||
|
--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="accompanying_course_work-list">
|
||||||
|
<table class="obj-res-eval my-3">
|
||||||
|
<thead>
|
||||||
|
<th class="eval">
|
||||||
|
<h4 class="title_label">{{ 'chill_calendar.Documents'|trans }}</h4>
|
||||||
|
</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for d in calendar.documents %}
|
||||||
|
<tr>
|
||||||
|
<td class="eval">
|
||||||
|
<ul class="eval_title">
|
||||||
|
<li>
|
||||||
|
{{ mm.mimeIcon(d.storedObject.type) }}
|
||||||
|
{{ d.storedObject.title }}
|
||||||
|
|
||||||
|
<ul class="record_actions small inline">
|
||||||
|
{% if chill_document_is_editable(d.storedObject) %}
|
||||||
|
<li>
|
||||||
|
{{ d.storedObject|chill_document_edit_button }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
{{ m.download_button(d.storedObject, d.storedObject.title) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@ -77,11 +77,12 @@
|
|||||||
{{ 'Cancel'|trans|chill_return_path_label }}
|
{{ 'Cancel'|trans|chill_return_path_label }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button class="btn btn-create" type="submit">
|
{{ form_widget(form.save_and_create_doc, { 'attr' : { 'class' : 'btn btn-create' }, 'label': 'chill_calendar.Save and add a document'|trans }) }}
|
||||||
{{ 'Save'|trans }}
|
</li>
|
||||||
</button>
|
<li>
|
||||||
</li>
|
{{ form_widget(form.save, { 'attr' : { 'class' : 'btn btn-create' }, 'label': 'Save'|trans }) }}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||||
|
|
||||||
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
|
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
|
||||||
|
|
||||||
@ -10,186 +10,207 @@
|
|||||||
{% block js %}
|
{% block js %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_script_tags('mod_answer') }}
|
{{ encore_entry_script_tags('mod_answer') }}
|
||||||
|
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_link_tags('mod_answer') }}
|
{{ encore_entry_link_tags('mod_answer') }}
|
||||||
|
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h1>{{ 'Calendar list' |trans }}</h1>
|
<h1>{{ 'Calendar list' |trans }}</h1>
|
||||||
{{ filterOrder|chill_render_filter_order_helper }}
|
{{ filterOrder|chill_render_filter_order_helper }}
|
||||||
|
|
||||||
{% if calendarItems|length == 0 %}
|
{% if calendarItems|length == 0 %}
|
||||||
<p class="chill-no-data-statement">
|
<p class="chill-no-data-statement">
|
||||||
{{ "There is no calendar items."|trans }}
|
{{ "There is no calendar items."|trans }}
|
||||||
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create button-small"></a>
|
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}"
|
||||||
</p>
|
class="btn btn-create button-small"></a>
|
||||||
{% else %}
|
</p>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
<div class="flex-table list-records context-accompanyingCourse">
|
<div class="flex-table list-records context-accompanyingCourse">
|
||||||
|
|
||||||
{% for calendar in calendarItems %}
|
{% for calendar in calendarItems %}
|
||||||
|
|
||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
<div class="item-row main">
|
<div class="item-row main">
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<div class="wrap-header">
|
<div class="wrap-header">
|
||||||
<div class="wl-row">
|
<div class="wl-row">
|
||||||
<div class="wl-col title">
|
<div class="wl-col title">
|
||||||
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
||||||
<p class="date-label">{{ calendar.startDate|format_datetime('short', 'short') }} - {{ calendar.endDate|format_datetime('short', 'short') }}</p>
|
<p class="date-label">{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||||
{% else %}
|
- {{ calendar.endDate|format_datetime('short', 'short') }}</p>
|
||||||
<p class="date-label">{{ calendar.startDate|format_datetime('short', 'short') }} - {{ calendar.endDate|format_datetime('none', 'short') }}</p>
|
{% else %}
|
||||||
{% endif %}
|
<p class="date-label">{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||||
|
- {{ calendar.endDate|format_datetime('none', 'short') }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="duration short-message">
|
<div class="duration short-message">
|
||||||
<p>
|
|
||||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||||
{{ calendar.duration|date('%H:%I')}}
|
{{ calendar.duration|date('%H:%I') }}
|
||||||
</p>
|
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
|
||||||
</div>
|
<!-- 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>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<ul class="list-content">
|
<ul class="list-content">
|
||||||
{% if calendar.mainUser is not empty %}
|
{% if calendar.mainUser is not empty %}
|
||||||
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box }}</span>
|
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</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 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
|
||||||
'context': '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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if calendar.location is not empty %}
|
{% if calendar.comment.comment is not empty
|
||||||
<div class="item-row separator">
|
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 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||||
|
'context': '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>
|
<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 %}
|
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
|
||||||
{% 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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
<div class="item-row">
|
||||||
<div>
|
<ul class="record_actions">
|
||||||
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
|
{% if is_granted('CHILL_CALENDAR_CALENDAR_SEE', calendar) and hasDocs %}
|
||||||
<span title="{{ 'Will not send SMS'|trans }}" class="fa-stack">
|
<li>
|
||||||
<i class="fa fa-envelope fa-stack-1x"></i>
|
<a class="btn btn-create"
|
||||||
<i class="fa fa-ban text-danger fa-stack-2x"></i>
|
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_pick_template', {'id': calendar.id }) }}">
|
||||||
</span>
|
{{ 'chill_calendar.Add a document'|trans }}
|
||||||
{% else %}
|
</a>
|
||||||
{% if calendar.smsStatus == 'sms_sent' %}
|
</li>
|
||||||
<span title="{{ 'SMS already sent'|trans }}">
|
|
||||||
<i class="fa fa-envelope"></i>
|
|
||||||
<i class="fa fa-check"></i>
|
|
||||||
</span>
|
|
||||||
{% else %}
|
|
||||||
<span title="{{ 'Will send SMS'|trans }}">
|
|
||||||
<i class="fa fa-bell"></i>
|
|
||||||
<i class="fa fa-envelope"></i>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) and calendar.activity is null %}
|
||||||
</div>
|
<li>
|
||||||
|
<a class="btn btn-create"
|
||||||
<ul class="record_actions">
|
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
|
||||||
{% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) %}
|
{{ 'Transform to activity'|trans }}
|
||||||
<li>
|
</a>
|
||||||
<a class="btn btn-create" href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
|
</li>
|
||||||
{{ 'Transform to activity'|trans }}
|
{% endif %}
|
||||||
</a>
|
{% if (calendar.isInvited(app.user)) %}
|
||||||
</li>
|
{% set invite = calendar.inviteForUser(app.user) %}
|
||||||
{% endif %}
|
<li>
|
||||||
{% if (calendar.isInvited(app.user)) %}
|
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
|
||||||
{% set invite = calendar.inviteForUser(app.user) %}
|
data-calendar-id="{{ calendar.id|e('html_attr') }}"></div>
|
||||||
<li>
|
</li>
|
||||||
<div invite-answer data-status="{{ invite.status|e('html_attr') }}" data-calendar-id="{{ calendar.id|e('html_attr') }}"></div>
|
{% endif %}
|
||||||
</li>
|
{% if false %}
|
||||||
{% endif %}
|
<li>
|
||||||
{% if false %}
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_show', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||||
<li>
|
class="btn btn-show "></a>
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_show', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-show "></a>
|
</li>
|
||||||
</li>
|
{% endif %}
|
||||||
{% endif %}
|
{# TOOD
|
||||||
{# TOOD
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %}
|
{% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %}
|
||||||
#}
|
#}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-update "></a>
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||||
</li>
|
class="btn btn-update "></a>
|
||||||
{# TOOD
|
</li>
|
||||||
|
{# TOOD
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %}
|
{% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %}
|
||||||
#}
|
#}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id, 'user_id' : user_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-delete "></a>
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id, 'user_id' : user_id, 'accompanying_period_id': accompanying_course_id } ) }}"
|
||||||
</li>
|
class="btn btn-delete "></a>
|
||||||
{#
|
</li>
|
||||||
{% endif %}
|
{#
|
||||||
#}
|
{% endif %}
|
||||||
</ul>
|
#}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
{% if calendarItems|length < paginator.getTotalItems %}
|
||||||
{% endfor %}
|
{{ chill_pagination(paginator) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if calendarItems|length < paginator.getTotalItems %}
|
</div>
|
||||||
{{ chill_pagination(paginator) }}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
{% if accompanyingCourse.user is not same as(null) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id, 'mainUser': accompanyingCourse.user.id }) }}"
|
||||||
|
class="btn btn-create">
|
||||||
|
{{ 'chill_calendar.Create for referrer'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
|
||||||
{% if accompanyingCourse.user is not same as(null) %}
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id, 'mainUser': accompanyingCourse.user.id }) }}" class="btn btn-create">
|
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}"
|
||||||
{{ 'chill_calendar.Create for referrer'|trans }}
|
class="btn btn-create">
|
||||||
|
{{ 'Create'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
</ul>
|
||||||
<li>
|
|
||||||
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create">
|
|
||||||
{{ 'Create'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -78,9 +78,10 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button class="btn btn-create" type="submit">
|
{{ form_widget(form.save_and_create_doc, { 'attr' : { 'class' : 'btn btn-create' }, 'label': 'chill_calendar.Create and add a document'|trans }) }}
|
||||||
{{ 'Create'|trans }}
|
</li>
|
||||||
</button>
|
<li>
|
||||||
|
{{ form_widget(form.save, { 'attr' : { 'class' : 'btn btn-create' }, 'label': 'Create'|trans }) }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||||
|
|
||||||
|
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'chill_calendar.Add a document' |trans }}{% endblock title %}
|
||||||
|
|
||||||
|
{% set user_id = null %}
|
||||||
|
{% set accompanying_course_id = accompanyingCourse.id %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-id="{{ calendar.id }}" data-entity-class="{{ 'Chill\\CalendarBundle\\Entity\\Calendar'|e('html_attr') }}"></div>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -0,0 +1,249 @@
|
|||||||
|
<?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\Service\DocGenerator;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Templating\Entity\PersonRender;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
use function count;
|
||||||
|
|
||||||
|
final class CalendarContext implements CalendarContextInterface
|
||||||
|
{
|
||||||
|
private BaseContextData $baseContextData;
|
||||||
|
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
private PersonRender $personRender;
|
||||||
|
|
||||||
|
private ThirdPartyRender $thirdPartyRender;
|
||||||
|
|
||||||
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
BaseContextData $baseContextData,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
NormalizerInterface $normalizer,
|
||||||
|
PersonRender $personRender,
|
||||||
|
ThirdPartyRender $thirdPartyRender,
|
||||||
|
TranslatableStringHelperInterface $translatableStringHelper
|
||||||
|
) {
|
||||||
|
$this->baseContextData = $baseContextData;
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->normalizer = $normalizer;
|
||||||
|
$this->personRender = $personRender;
|
||||||
|
$this->thirdPartyRender = $thirdPartyRender;
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function adminFormReverseTransform(array $data): array
|
||||||
|
{
|
||||||
|
return array_merge(
|
||||||
|
[
|
||||||
|
'trackDatetime' => true,
|
||||||
|
'askMainPerson' => true,
|
||||||
|
'mainPersonLabel' => 'docgen.calendar.Destinee',
|
||||||
|
'askThirdParty' => false,
|
||||||
|
'thirdPartyLabel' => 'Third party',
|
||||||
|
],
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function adminFormTransform(array $data): array
|
||||||
|
{
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildAdminForm(FormBuilderInterface $builder): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('trackDatetime', CheckboxType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'label' => 'docgen.calendar.Track changes on datetime and warn user if date time is updated after the doc generation',
|
||||||
|
])
|
||||||
|
->add('askMainPerson', CheckboxType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'label' => 'docgen.calendar.Ask main person',
|
||||||
|
])
|
||||||
|
->add('mainPersonLabel', TextType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'label' => 'docgen.calendar.Main person label',
|
||||||
|
])
|
||||||
|
->add('askThirdParty', CheckboxType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'label' => 'docgen.calendar.Ask third party',
|
||||||
|
])
|
||||||
|
->add('thirdPartyLabel', TextType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'label' => 'docgen.calendar.Third party label',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
|
||||||
|
{
|
||||||
|
$options = $this->getOptions($template);
|
||||||
|
|
||||||
|
$builder->add('title', TextType::class, [
|
||||||
|
'required' => true,
|
||||||
|
'label' => 'docgen.calendar.title of the generated document',
|
||||||
|
'data' => $this->translatableStringHelper->localize($template->getName()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($options['askMainPerson']) {
|
||||||
|
$builder->add('mainPerson', EntityType::class, [
|
||||||
|
'class' => Person::class,
|
||||||
|
'multiple' => false,
|
||||||
|
'label' => $options['mainPersonLabel'] ?? 'docgen.calendar.Main person label',
|
||||||
|
'required' => false,
|
||||||
|
'choices' => $entity->getPersons(),
|
||||||
|
'choice_label' => fn (Person $p) => $this->personRender->renderString($p, []),
|
||||||
|
'expanded' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($options['askThirdParty']) {
|
||||||
|
$builder->add('thirdParty', EntityType::class, [
|
||||||
|
'class' => ThirdParty::class,
|
||||||
|
'multiple' => false,
|
||||||
|
'label' => $options['thirdPartyLabel'] ?? 'Third party',
|
||||||
|
'choices' => $entity->getProfessionals(),
|
||||||
|
'choice_label' => fn (ThirdParty $tp) => $this->thirdPartyRender->renderString($tp, []),
|
||||||
|
'expanded' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{mainPerson?: Person, thirdParty?: ThirdParty, title: string} $contextGenerationData
|
||||||
|
* @param mixed $entity
|
||||||
|
*/
|
||||||
|
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
|
||||||
|
{
|
||||||
|
$options = $this->getOptions($template);
|
||||||
|
|
||||||
|
$data = array_merge(
|
||||||
|
$this->baseContextData->getData(),
|
||||||
|
[
|
||||||
|
'calendar' => $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => Calendar::class, 'groups' => ['docgen:read']]),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($options['askMainPerson']) {
|
||||||
|
$data['mainPerson'] = $this->normalizer->normalize($contextGenerationData['mainPerson'] ?? null, 'docgen', [
|
||||||
|
'docgen:expects' => Person::class,
|
||||||
|
'groups' => ['docgen:read'],
|
||||||
|
'docgen:person:with-household' => true,
|
||||||
|
'docgen:person:with-relations' => true,
|
||||||
|
'docgen:person:with-budget' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($options['askThirdParty']) {
|
||||||
|
$data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'] ?? null, 'docgen', [
|
||||||
|
'docgen:expects' => ThirdParty::class,
|
||||||
|
'groups' => ['docgen:read'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'docgen.calendar.A base context for generating document on calendar';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityClass(): string
|
||||||
|
{
|
||||||
|
return Calendar::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormData(DocGeneratorTemplate $template, $entity): array
|
||||||
|
{
|
||||||
|
$options = $this->getOptions($template);
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
if ($options['askMainPerson']) {
|
||||||
|
$data['mainPerson'] = null;
|
||||||
|
|
||||||
|
if (1 === count($entity->getPersons())) {
|
||||||
|
$data['mainPerson'] = $entity->getPersons()->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($options['askThirdParty']) {
|
||||||
|
$data['thirdParty'] = null;
|
||||||
|
|
||||||
|
if (1 === count($entity->getProfessionals())) {
|
||||||
|
$data['thirdParty'] = $entity->getProfessionals()->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getKey(): string
|
||||||
|
{
|
||||||
|
return self::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'docgen.calendar.Base context for calendar';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAdminForm(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{mainPerson?: Person, thirdParty?: ThirdParty, title: string} $contextGenerationData
|
||||||
|
*/
|
||||||
|
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
|
||||||
|
{
|
||||||
|
$options = $this->getOptions($template);
|
||||||
|
$storedObject->setTitle($contextGenerationData['title']);
|
||||||
|
$doc = new CalendarDoc($entity, $storedObject);
|
||||||
|
$doc->setTrackDateTimeVersion($options['trackDatetime']);
|
||||||
|
|
||||||
|
$this->entityManager->persist($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{askMainPerson: bool, mainPersonLabel: ?string, askThirdParty: bool, thirdPartyLabel: ?string, trackDateTime: bool} $options
|
||||||
|
*/
|
||||||
|
private function getOptions(DocGeneratorTemplate $template): array
|
||||||
|
{
|
||||||
|
return $template->getOptions();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
<?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\Service\DocGenerator;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
|
||||||
|
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
||||||
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
interface CalendarContextInterface extends DocGeneratorContextWithPublicFormInterface, DocGeneratorContextWithAdminFormInterface
|
||||||
|
{
|
||||||
|
public function adminFormReverseTransform(array $data): array;
|
||||||
|
|
||||||
|
public function adminFormTransform(array $data): array;
|
||||||
|
|
||||||
|
public function buildAdminForm(FormBuilderInterface $builder): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Calendar $entity
|
||||||
|
*/
|
||||||
|
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Calendar $entity
|
||||||
|
*/
|
||||||
|
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array;
|
||||||
|
|
||||||
|
public function getDescription(): string;
|
||||||
|
|
||||||
|
public function getEntityClass(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Calendar $entity
|
||||||
|
*/
|
||||||
|
public function getFormData(DocGeneratorTemplate $template, $entity): array;
|
||||||
|
|
||||||
|
public static function getKey(): string;
|
||||||
|
|
||||||
|
public function getName(): string;
|
||||||
|
|
||||||
|
public function hasAdminForm(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Calendar $entity
|
||||||
|
*/
|
||||||
|
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Calendar $entity
|
||||||
|
*/
|
||||||
|
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void;
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
<?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\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class CalendarNormalizerTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
|
||||||
|
$this->normalizer = self::$container->get(NormalizerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizationCalendar()
|
||||||
|
{
|
||||||
|
$calendar = (new Calendar())
|
||||||
|
->setComment(
|
||||||
|
$comment = new CommentEmbeddable()
|
||||||
|
)
|
||||||
|
->setStartDate(DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, '2020-10-15T15:00:00+0000'))
|
||||||
|
->setEndDate(DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, '2020-15-15T15:30:00+0000'))
|
||||||
|
->addPerson(new Person())
|
||||||
|
->addPerson(new Person())
|
||||||
|
->addUser(new User())
|
||||||
|
->addProfessional(new ThirdParty());
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'type' => 'chill_calendar_calendar',
|
||||||
|
'isNull' => false,
|
||||||
|
'urgent' => false,
|
||||||
|
'sendSMS' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$actual = $this->normalizer->normalize(
|
||||||
|
$calendar,
|
||||||
|
'docgen',
|
||||||
|
['groups' => ['docgen:read'], 'docgen:expects' => Calendar::class]
|
||||||
|
);
|
||||||
|
|
||||||
|
// we first check for the known key/value...
|
||||||
|
foreach ($expected as $key => $value) {
|
||||||
|
$this->assertArrayHasKey($key, $actual);
|
||||||
|
$this->assertEquals($value, $actual[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and then check for some other values
|
||||||
|
$this->assertArrayHasKey('persons', $actual);
|
||||||
|
$this->assertIsArray($actual['persons']);
|
||||||
|
$this->assertArrayHasKey('invites', $actual);
|
||||||
|
$this->assertIsArray($actual['invites']);
|
||||||
|
$this->assertArrayHasKey('startDate', $actual);
|
||||||
|
$this->assertIsArray($actual['startDate']);
|
||||||
|
$this->assertArrayHasKey('endDate', $actual);
|
||||||
|
$this->assertIsArray($actual['endDate']);
|
||||||
|
$this->assertArrayHasKey('professionals', $actual);
|
||||||
|
$this->assertIsArray($actual['professionals']);
|
||||||
|
$this->assertArrayHasKey('location', $actual);
|
||||||
|
$this->assertIsArray($actual['location']);
|
||||||
|
$this->assertArrayHasKey('mainUser', $actual);
|
||||||
|
$this->assertIsArray($actual['mainUser']);
|
||||||
|
$this->assertArrayHasKey('comment', $actual);
|
||||||
|
$this->assertIsArray($actual['comment']);
|
||||||
|
$this->assertArrayHasKey('duration', $actual);
|
||||||
|
$this->assertIsArray($actual['duration']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizationOnNullHasSameKeys()
|
||||||
|
{
|
||||||
|
$calendar = new Calendar();
|
||||||
|
|
||||||
|
$notNullCalendar = $this->normalizer->normalize(
|
||||||
|
$calendar,
|
||||||
|
'docgen',
|
||||||
|
['groups' => ['docgen:read'], 'docgen:expects' => Calendar::class]
|
||||||
|
);
|
||||||
|
|
||||||
|
$isNullCalendar = $this->normalizer->normalize(
|
||||||
|
null,
|
||||||
|
'docgen',
|
||||||
|
['groups' => ['docgen:read'], 'docgen:expects' => Calendar::class]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEqualsCanonicalizing(array_keys($notNullCalendar), array_keys($isNullCalendar));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
<?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\Service\DocGenerator;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Chill\CalendarBundle\Service\DocGenerator\CalendarContext;
|
||||||
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Templating\Entity\PersonRender;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\Exception\Prediction\FailedPredictionException;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
use function array_key_exists;
|
||||||
|
use function count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class CalendarContextTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
public function testAdminFormReverseTransform()
|
||||||
|
{
|
||||||
|
$expected =
|
||||||
|
[
|
||||||
|
'track_datetime' => true,
|
||||||
|
'askMainPerson' => true,
|
||||||
|
'mainPersonLabel' => 'docgen.calendar.Destinee',
|
||||||
|
'askThirdParty' => false,
|
||||||
|
'thirdPartyLabel' => 'Third party',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEqualsCanonicalizing($expected, $this->buildCalendarContext()->adminFormReverseTransform([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdminFormTransform()
|
||||||
|
{
|
||||||
|
$expected =
|
||||||
|
[
|
||||||
|
'track_datetime' => true,
|
||||||
|
'askMainPerson' => true,
|
||||||
|
'mainPersonLabel' => 'docgen.calendar.Destinee',
|
||||||
|
'askThirdParty' => false,
|
||||||
|
'thirdPartyLabel' => 'Third party',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEqualsCanonicalizing($expected, $this->buildCalendarContext()->adminFormTransform($expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBuildPublicForm()
|
||||||
|
{
|
||||||
|
$formBuilder = $this->prophesize(FormBuilderInterface::class);
|
||||||
|
$calendar = new Calendar();
|
||||||
|
$calendar
|
||||||
|
->addProfessional($tp1 = new ThirdParty())
|
||||||
|
->addProfessional($tp2 = new ThirdParty())
|
||||||
|
->addPerson($p1 = new Person());
|
||||||
|
|
||||||
|
// we will try once with askThirdParty = true, once with askPerson = true, and once with both
|
||||||
|
// so, we expect the call to be twice for each method
|
||||||
|
$formBuilder->add('thirdParty', EntityType::class, Argument::type('array'))
|
||||||
|
->should(static function ($calls, $object, $method) use ($tp1, $tp2) {
|
||||||
|
if (2 !== count($calls)) {
|
||||||
|
throw new FailedPredictionException(sprintf('the $builder->add should be called exactly 2, %d receivved', count($calls)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$opts = $calls[0]->getArguments()[2];
|
||||||
|
|
||||||
|
if (!array_key_exists('label', $opts)) {
|
||||||
|
throw new FailedPredictionException('the $builder->add should have a label key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('tplabel' !== $opts['label']) {
|
||||||
|
throw new FailedPredictionException('third party label not expected');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$opts['choices']->contains($tp1) || !$opts['choices']->contains($tp2)) {
|
||||||
|
throw new FailedPredictionException('third party not present');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$formBuilder->add('mainPerson', EntityType::class, Argument::type('array'))
|
||||||
|
->should(static function ($calls, $object, $method) use ($p1) {
|
||||||
|
if (2 !== count($calls)) {
|
||||||
|
throw new FailedPredictionException(sprintf('the $builder->add should be called exactly 2, %d receivved', count($calls)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$opts = $calls[0]->getArguments()[2];
|
||||||
|
|
||||||
|
if (!array_key_exists('label', $opts)) {
|
||||||
|
throw new FailedPredictionException('the $builder->add should have a label key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('personLabel' !== $opts['label']) {
|
||||||
|
throw new FailedPredictionException('person label not expected');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$opts['choices']->contains($p1)) {
|
||||||
|
throw new FailedPredictionException('person not present');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$formBuilder->add('title', TextType::class, Argument::type('array'))
|
||||||
|
->shouldBeCalledTimes(3);
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
['askMainPerson' => true, 'mainPersonLabel' => 'personLabel', 'askThirdParty' => true, 'thirdPartyLabel' => 'tplabel'],
|
||||||
|
['askMainPerson' => false, 'mainPersonLabel' => 'personLabel', 'askThirdParty' => true, 'thirdPartyLabel' => 'tplabel'],
|
||||||
|
['askMainPerson' => true, 'mainPersonLabel' => 'personLabel', 'askThirdParty' => false, 'thirdPartyLabel' => 'tplabel'],
|
||||||
|
] as $options) {
|
||||||
|
$template = new DocGeneratorTemplate();
|
||||||
|
$template->setOptions($options);
|
||||||
|
|
||||||
|
$this->buildCalendarContext()->buildPublicForm($formBuilder->reveal(), $template, $calendar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetData()
|
||||||
|
{
|
||||||
|
$calendar = (new Calendar())
|
||||||
|
->addPerson($p1 = new Person())
|
||||||
|
->addProfessional($t1 = new ThirdParty());
|
||||||
|
$template = (new DocGeneratorTemplate())->setOptions(
|
||||||
|
['askMainPerson' => true, 'mainPersonLabel' => 'personLabel', 'askThirdParty' => true, 'thirdPartyLabel' => 'tplabel'],
|
||||||
|
);
|
||||||
|
$contextData = [
|
||||||
|
'mainPerson' => $p1,
|
||||||
|
'thirdParty' => $t1,
|
||||||
|
];
|
||||||
|
|
||||||
|
$normalizer = $this->prophesize(NormalizerInterface::class);
|
||||||
|
$normalizer->normalize($p1, 'docgen', Argument::type('array'))->willReturn(['person' => '1']);
|
||||||
|
$normalizer->normalize($t1, 'docgen', Argument::type('array'))->willReturn(['tp' => '1']);
|
||||||
|
$normalizer->normalize($calendar, 'docgen', Argument::type('array'))->willReturn(['calendar' => '1']);
|
||||||
|
|
||||||
|
$actual = $this->buildCalendarContext(null, $normalizer->reveal())
|
||||||
|
->getData($template, $calendar, $contextData);
|
||||||
|
|
||||||
|
$this->assertEqualsCanonicalizing([
|
||||||
|
'calendar' => ['calendar' => '1'],
|
||||||
|
'mainPerson' => ['person' => '1'],
|
||||||
|
'thirdParty' => ['tp' => '1'],
|
||||||
|
'base_context' => 'data',
|
||||||
|
], $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStoreGenerated()
|
||||||
|
{
|
||||||
|
$calendar = new Calendar();
|
||||||
|
$storedObject = new StoredObject();
|
||||||
|
$contextData = ['title' => 'blabla'];
|
||||||
|
$template = (new DocGeneratorTemplate())->setOptions(['trackDatetime' => true]);
|
||||||
|
|
||||||
|
$em = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$em->persist(Argument::type(CalendarDoc::class))->should(
|
||||||
|
static function ($calls, $object, $method) use ($storedObject) {
|
||||||
|
if (1 !== count($calls)) {
|
||||||
|
throw new FailedPredictionException('the persist method should be called once');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var CalendarDoc $calendarDoc */
|
||||||
|
$calendarDoc = $calls[0]->getArguments()[0];
|
||||||
|
|
||||||
|
if ($calendarDoc->getStoredObject() !== $storedObject) {
|
||||||
|
throw new FailedPredictionException('the stored object is not correct');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($calendarDoc->getStoredObject()->getTitle() !== 'blabla') {
|
||||||
|
throw new FailedPredictionException('the doc title should be the one provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$calendarDoc->isTrackDateTimeVersion()) {
|
||||||
|
throw new FailedPredictionException('the track date time should be true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->buildCalendarContext($em->reveal())->storeGenerated($template, $storedObject, $calendar, $contextData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCalendarContext(
|
||||||
|
?EntityManagerInterface $entityManager = null,
|
||||||
|
?NormalizerInterface $normalizer = null
|
||||||
|
): CalendarContext {
|
||||||
|
$baseContext = $this->prophesize(BaseContextData::class);
|
||||||
|
$baseContext->getData()->willReturn(['base_context' => 'data']);
|
||||||
|
|
||||||
|
$personRender = $this->prophesize(PersonRender::class);
|
||||||
|
$personRender->renderString(Argument::type(Person::class), [])->willReturn('person name');
|
||||||
|
|
||||||
|
$thirdPartyRender = $this->prophesize(ThirdPartyRender::class);
|
||||||
|
$thirdPartyRender->renderString(Argument::type(ThirdParty::class), [])->willReturn('third party name');
|
||||||
|
|
||||||
|
$translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
|
||||||
|
$translatableStringHelper->localize(Argument::type('array'))->willReturn('blabla');
|
||||||
|
|
||||||
|
if (null === $normalizer) {
|
||||||
|
$normalizer = $this->prophesize(NormalizerInterface::class)->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $entityManager) {
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class)->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CalendarContext(
|
||||||
|
$baseContext->reveal(),
|
||||||
|
$entityManager,
|
||||||
|
$normalizer,
|
||||||
|
$personRender->reveal(),
|
||||||
|
$thirdPartyRender->reveal(),
|
||||||
|
$translatableStringHelper->reveal()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
<?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\Migrations\Calendar;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20221020101547 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP SEQUENCE chill_calendar.calendar_doc_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP TABLE chill_calendar.calendar_doc');
|
||||||
|
$this->addSql('ALTER TABLE chill_calendar.calendar DROP dateTimeVersion');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add calendardoc on Calendar';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE SEQUENCE chill_calendar.calendar_doc_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE TABLE chill_calendar.calendar_doc (id INT NOT NULL, calendar_id INT NOT NULL, datetimeVersion INT DEFAULT 0 NOT NULL, trackDateTimeVersion BOOLEAN DEFAULT false NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, storedObject_id INT NOT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_4FD11573A40A2C8 ON chill_calendar.calendar_doc (calendar_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_4FD115736C99C13A ON chill_calendar.calendar_doc (storedObject_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_4FD115733174800F ON chill_calendar.calendar_doc (createdBy_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_4FD1157365FF1AEC ON chill_calendar.calendar_doc (updatedBy_id)');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_calendar.calendar_doc.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_calendar.calendar_doc.updatedAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('ALTER TABLE chill_calendar.calendar_doc ADD CONSTRAINT FK_4FD11573A40A2C8 FOREIGN KEY (calendar_id) REFERENCES chill_calendar.calendar (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_calendar.calendar_doc ADD CONSTRAINT FK_4FD115736C99C13A FOREIGN KEY (storedObject_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_calendar.calendar_doc ADD CONSTRAINT FK_4FD115733174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_calendar.calendar_doc ADD CONSTRAINT FK_4FD1157365FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_calendar.calendar ADD dateTimeVersion INT DEFAULT 0 NOT NULL');
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,11 @@ chill_calendar:
|
|||||||
From: Du
|
From: Du
|
||||||
To: Au
|
To: Au
|
||||||
Next calendars: Prochains rendez-vous
|
Next calendars: Prochains rendez-vous
|
||||||
|
Add a document: Ajouter un document
|
||||||
|
Documents: Documents
|
||||||
|
Create and add a document: Créer et ajouter un document
|
||||||
|
Save and add a document: Enregistrer et ajouter un document
|
||||||
|
|
||||||
|
|
||||||
remote_ms_graph:
|
remote_ms_graph:
|
||||||
freebusy_statuses:
|
freebusy_statuses:
|
||||||
@ -112,3 +117,15 @@ has calendar range: Dans une plage de disponibilité?
|
|||||||
Not made within a calendar range: Rendez-vous dans une plage de disponibilité
|
Not made within a calendar range: Rendez-vous dans une plage de disponibilité
|
||||||
Made within a calendar range: Rendez-vous en dehors d'une plage de disponibilité
|
Made within a calendar range: Rendez-vous en dehors d'une plage de disponibilité
|
||||||
|
|
||||||
|
docgen:
|
||||||
|
calendar:
|
||||||
|
Base context for calendar: 'Rendez-vous: contexte de base'
|
||||||
|
A base context for generating document on calendar: Contexte pour générer des documents à partir des rendez-vous
|
||||||
|
Track changes on datetime and warn user if date time is updated after the doc generation: Suivre les changements sur le document et prévenir les utilisateurs que la date et l'heure ont été modifiée après la génération du document
|
||||||
|
Ask main person: Demander de choisir une personne parmi les participants aux rendez-vous
|
||||||
|
Main person label: Label pour choisir la personne
|
||||||
|
Ask third party: Demander de choisir un tiers parmi les participants aux rendez-vous
|
||||||
|
Third party label: Label pour choisir le tiers
|
||||||
|
Destinee: Destinataire
|
||||||
|
None: Aucun choix
|
||||||
|
title of the generated document: Titre du document généré
|
||||||
|
@ -14,6 +14,8 @@ namespace Chill\DocGeneratorBundle\Controller;
|
|||||||
use Chill\DocGeneratorBundle\Context\ContextManager;
|
use Chill\DocGeneratorBundle\Context\ContextManager;
|
||||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
@ -84,4 +86,16 @@ class AdminDocGeneratorTemplateController extends CRUDController
|
|||||||
|
|
||||||
return $entity;
|
return $entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param QueryBuilder $query
|
||||||
|
*
|
||||||
|
* @return QueryBuilder|mixed
|
||||||
|
*/
|
||||||
|
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||||
|
{
|
||||||
|
return $query->addSelect('JSON_EXTRACT(e.name, :lang) AS HIDDEN name_lang')
|
||||||
|
->setParameter('lang', $request->getLocale())
|
||||||
|
->addOrderBy('name_lang', 'ASC');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,18 @@ use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
|||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
|
||||||
final class DocGeneratorTemplateRepository implements ObjectRepository
|
final class DocGeneratorTemplateRepository implements ObjectRepository
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
private RequestStack $requestStack;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager, RequestStack $requestStack)
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(DocGeneratorTemplate::class);
|
$this->repository = $entityManager->getRepository(DocGeneratorTemplate::class);
|
||||||
|
$this->requestStack = $requestStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countByEntity(string $entity): int
|
public function countByEntity(string $entity): int
|
||||||
@ -32,6 +36,7 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
|
|||||||
$builder
|
$builder
|
||||||
->select('count(t)')
|
->select('count(t)')
|
||||||
->where('t.entity LIKE :entity')
|
->where('t.entity LIKE :entity')
|
||||||
|
->andWhere($builder->expr()->eq('t.active', "'TRUE'"))
|
||||||
->setParameter('entity', addslashes($entity));
|
->setParameter('entity', addslashes($entity));
|
||||||
|
|
||||||
return $builder->getQuery()->getSingleScalarResult();
|
return $builder->getQuery()->getSingleScalarResult();
|
||||||
@ -71,7 +76,10 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
|
|||||||
$builder
|
$builder
|
||||||
->where('t.entity LIKE :entity')
|
->where('t.entity LIKE :entity')
|
||||||
->andWhere($builder->expr()->eq('t.active', "'TRUE'"))
|
->andWhere($builder->expr()->eq('t.active', "'TRUE'"))
|
||||||
->setParameter('entity', addslashes($entity));
|
->setParameter('entity', addslashes($entity))
|
||||||
|
->addSelect('JSON_EXTRACT(t.name, :lang) AS HIDDEN name_lang')
|
||||||
|
->setParameter('lang', $this->requestStack->getCurrentRequest()->getLocale())
|
||||||
|
->addOrderBy('name_lang', 'ASC');
|
||||||
|
|
||||||
return $builder
|
return $builder
|
||||||
->getQuery()
|
->getQuery()
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
{% block title 'docgen.Generate a document'|trans %}
|
{% block title 'docgen.Generate a document'|trans %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ encore_entry_script_tags('mod_pickentity_type') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-md-10 col-xxl">
|
<div class="col-md-10 col-xxl">
|
||||||
<h1>{{ block('title') }}</h1>
|
<h1>{{ block('title') }}</h1>
|
||||||
|
@ -77,6 +77,19 @@ final class DocGenObjectNormalizerTest extends KernelTestCase
|
|||||||
$this->assertArrayNotHasKey('baz', $actual['child']);
|
$this->assertArrayNotHasKey('baz', $actual['child']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNormalizableBooleanPropertyOrMethodOnNull()
|
||||||
|
{
|
||||||
|
$actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableClassWithBool::class]);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'foo' => null,
|
||||||
|
'thing' => null,
|
||||||
|
'isNull' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
|
||||||
public function testNormalizationBasic()
|
public function testNormalizationBasic()
|
||||||
{
|
{
|
||||||
$scope = new Scope();
|
$scope = new Scope();
|
||||||
@ -93,6 +106,22 @@ final class DocGenObjectNormalizerTest extends KernelTestCase
|
|||||||
$this->assertEquals($expected, $normalized, 'test normalization fo a scope');
|
$this->assertEquals($expected, $normalized, 'test normalization fo a scope');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNormalizeBooleanPropertyOrMethod()
|
||||||
|
{
|
||||||
|
$testable = new TestableClassWithBool();
|
||||||
|
$testable->foo = false;
|
||||||
|
|
||||||
|
$actual = $this->normalizer->normalize($testable, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableClassWithBool::class]);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'foo' => false,
|
||||||
|
'thing' => true,
|
||||||
|
'isNull' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
|
||||||
public function testNormalizeNull()
|
public function testNormalizeNull()
|
||||||
{
|
{
|
||||||
$actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]);
|
$actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]);
|
||||||
@ -170,3 +199,19 @@ class TestableChildClass
|
|||||||
*/
|
*/
|
||||||
public string $foo = 'bar';
|
public string $foo = 'bar';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestableClassWithBool
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Serializer\Groups("docgen:read")
|
||||||
|
*/
|
||||||
|
public bool $foo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Serializer\Groups("docgen:read")
|
||||||
|
*/
|
||||||
|
public function getThing(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace Chill\MainBundle\Controller;
|
|||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Repository\CountryRepository;
|
use Chill\MainBundle\Repository\CountryRepository;
|
||||||
use Chill\MainBundle\Repository\PostalCodeRepository;
|
use Chill\MainBundle\Repository\PostalCodeRepositoryInterface;
|
||||||
use Chill\MainBundle\Serializer\Model\Collection;
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@ -30,11 +30,11 @@ final class PostalCodeAPIController extends ApiController
|
|||||||
|
|
||||||
private PaginatorFactory $paginatorFactory;
|
private PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
private PostalCodeRepository $postalCodeRepository;
|
private PostalCodeRepositoryInterface $postalCodeRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
CountryRepository $countryRepository,
|
CountryRepository $countryRepository,
|
||||||
PostalCodeRepository $postalCodeRepository,
|
PostalCodeRepositoryInterface $postalCodeRepository,
|
||||||
PaginatorFactory $paginatorFactory
|
PaginatorFactory $paginatorFactory
|
||||||
) {
|
) {
|
||||||
$this->countryRepository = $countryRepository;
|
$this->countryRepository = $countryRepository;
|
||||||
|
@ -27,6 +27,7 @@ use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
|
|||||||
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
|
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
|
||||||
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
|
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
|
||||||
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
|
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
|
||||||
|
use Chill\MainBundle\Doctrine\DQL\JsonExtract;
|
||||||
use Chill\MainBundle\Doctrine\DQL\OverlapsI;
|
use Chill\MainBundle\Doctrine\DQL\OverlapsI;
|
||||||
use Chill\MainBundle\Doctrine\DQL\Replace;
|
use Chill\MainBundle\Doctrine\DQL\Replace;
|
||||||
use Chill\MainBundle\Doctrine\DQL\Similarity;
|
use Chill\MainBundle\Doctrine\DQL\Similarity;
|
||||||
@ -234,6 +235,7 @@ class ChillMainExtension extends Extension implements
|
|||||||
'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class,
|
'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class,
|
||||||
'AGGREGATE' => JsonAggregate::class,
|
'AGGREGATE' => JsonAggregate::class,
|
||||||
'REPLACE' => Replace::class,
|
'REPLACE' => Replace::class,
|
||||||
|
'JSON_EXTRACT' => JsonExtract::class,
|
||||||
],
|
],
|
||||||
'numeric_functions' => [
|
'numeric_functions' => [
|
||||||
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class,
|
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class,
|
||||||
|
43
src/Bundle/ChillMainBundle/Doctrine/DQL/JsonExtract.php
Normal file
43
src/Bundle/ChillMainBundle/Doctrine/DQL/JsonExtract.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?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\MainBundle\Doctrine\DQL;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||||
|
use Doctrine\ORM\Query\Lexer;
|
||||||
|
use Doctrine\ORM\Query\Parser;
|
||||||
|
use Doctrine\ORM\Query\SqlWalker;
|
||||||
|
|
||||||
|
class JsonExtract extends FunctionNode
|
||||||
|
{
|
||||||
|
private $element;
|
||||||
|
|
||||||
|
private $keyToExtract;
|
||||||
|
|
||||||
|
public function getSql(SqlWalker $sqlWalker)
|
||||||
|
{
|
||||||
|
return sprintf('%s->>%s', $this->element->dispatch($sqlWalker), $this->keyToExtract->dispatch($sqlWalker));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse(Parser $parser)
|
||||||
|
{
|
||||||
|
$parser->match(Lexer::T_IDENTIFIER);
|
||||||
|
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||||
|
|
||||||
|
$this->element = $parser->ArithmeticPrimary();
|
||||||
|
|
||||||
|
$parser->match(Lexer::T_COMMA);
|
||||||
|
|
||||||
|
$this->keyToExtract = $parser->ArithmeticExpression();
|
||||||
|
|
||||||
|
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
<?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\MainBundle\Form\Type\DataTransformer;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Repository\PostalCodeRepositoryInterface;
|
||||||
|
use Symfony\Component\Form\DataTransformerInterface;
|
||||||
|
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||||
|
use function gettype;
|
||||||
|
use function is_int;
|
||||||
|
|
||||||
|
class PostalCodeToIdTransformer implements DataTransformerInterface
|
||||||
|
{
|
||||||
|
private PostalCodeRepositoryInterface $postalCodeRepository;
|
||||||
|
|
||||||
|
public function __construct(PostalCodeRepositoryInterface $postalCodeRepository)
|
||||||
|
{
|
||||||
|
$this->postalCodeRepository = $postalCodeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseTransform($value)
|
||||||
|
{
|
||||||
|
if (null === $value || trim('') === $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_int((int) $value)) {
|
||||||
|
throw new TransformationFailedException('Cannot transform ' . gettype($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->postalCodeRepository->find((int) $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform($value)
|
||||||
|
{
|
||||||
|
if (null === $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof PostalCode) {
|
||||||
|
return $value->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TransformationFailedException('Could not reverseTransform ' . gettype($value));
|
||||||
|
}
|
||||||
|
}
|
49
src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php
Normal file
49
src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?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\MainBundle\Form\Type;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Form\Type\DataTransformer\PostalCodeToIdTransformer;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
use Symfony\Component\Form\FormView;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class PickPostalCodeType extends AbstractType
|
||||||
|
{
|
||||||
|
private PostalCodeToIdTransformer $postalCodeToIdTransformer;
|
||||||
|
|
||||||
|
public function __construct(PostalCodeToIdTransformer $postalCodeToIdTransformer)
|
||||||
|
{
|
||||||
|
$this->postalCodeToIdTransformer = $postalCodeToIdTransformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder->addViewTransformer($this->postalCodeToIdTransformer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||||
|
{
|
||||||
|
$view->vars['uniqid'] = $view->vars['attr']['data-input-postal-code'] = uniqid('input_pick_postal_code_');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver
|
||||||
|
->setDefault('class', PostalCode::class)
|
||||||
|
->setDefault('multiple', false)
|
||||||
|
->setAllowedTypes('multiple', ['bool'])
|
||||||
|
->setDefault('compound', false);
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,9 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\Query\ResultSetMapping;
|
use Doctrine\ORM\Query\ResultSetMapping;
|
||||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
final class PostalCodeRepository implements ObjectRepository
|
final class PostalCodeRepository implements PostalCodeRepositoryInterface
|
||||||
{
|
{
|
||||||
private EntityManagerInterface $entityManager;
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ final class PostalCodeRepository implements ObjectRepository
|
|||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(PostalCode::class);
|
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||||
$this->entityManager = $entityManager;
|
$this->entityManager = $entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,20 +50,11 @@ final class PostalCodeRepository implements ObjectRepository
|
|||||||
return $this->repository->find($id, $lockMode, $lockVersion);
|
return $this->repository->find($id, $lockMode, $lockVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return PostalCode[]
|
|
||||||
*/
|
|
||||||
public function findAll(): array
|
public function findAll(): array
|
||||||
{
|
{
|
||||||
return $this->repository->findAll();
|
return $this->repository->findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed|null $limit
|
|
||||||
* @param mixed|null $offset
|
|
||||||
*
|
|
||||||
* @return PostalCode[]
|
|
||||||
*/
|
|
||||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||||
{
|
{
|
||||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
@ -95,7 +85,7 @@ final class PostalCodeRepository implements ObjectRepository
|
|||||||
return $this->repository->findOneBy($criteria, $orderBy);
|
return $this->repository->findOneBy($criteria, $orderBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getClassName()
|
public function getClassName(): string
|
||||||
{
|
{
|
||||||
return PostalCode::class;
|
return PostalCode::class;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
<?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\MainBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Country;
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
interface PostalCodeRepositoryInterface extends ObjectRepository
|
||||||
|
{
|
||||||
|
public function countByPattern(string $pattern, ?Country $country): int;
|
||||||
|
|
||||||
|
public function find($id, $lockMode = null, $lockVersion = null): ?PostalCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PostalCode[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed|null $limit
|
||||||
|
* @param mixed|null $offset
|
||||||
|
*
|
||||||
|
* @return PostalCode[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
|
||||||
|
|
||||||
|
public function findByPattern(string $pattern, ?Country $country, ?int $start = 0, ?int $limit = 50): array;
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria, ?array $orderBy = null): ?PostalCode;
|
||||||
|
|
||||||
|
public function getClassName(): string;
|
||||||
|
}
|
@ -5,6 +5,10 @@ ul.record_actions {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
|
|
||||||
|
&.inline {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
&.column {
|
&.column {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@ -18,6 +22,13 @@ ul.record_actions {
|
|||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
.btn {
|
||||||
|
padding: .25rem .5rem;
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
import { createApp } from 'vue';
|
||||||
|
import PickPostalCode from 'ChillMainAssets/vuejs/PickPostalCode/PickPostalCode';
|
||||||
|
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
||||||
|
import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n';
|
||||||
|
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
|
||||||
|
|
||||||
|
const i18n = _createI18n(appMessages);
|
||||||
|
|
||||||
|
|
||||||
|
function loadOnePicker(el, input, uniqId, city) {
|
||||||
|
const app = createApp({
|
||||||
|
template: '<pick-postal-code @select-city="onCitySelected" @removeCity="onCityRemoved" :picked="city"></pick-postal-code>',
|
||||||
|
components: {
|
||||||
|
PickPostalCode,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
city: city,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onCitySelected(city) {
|
||||||
|
this.city = city;
|
||||||
|
input.value = city.id;
|
||||||
|
},
|
||||||
|
onCityRemoved(city) {
|
||||||
|
this.city = null;
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.use(i18n)
|
||||||
|
.mount(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadDynamicPickers(element) {
|
||||||
|
|
||||||
|
let apps = element.querySelectorAll('[data-module="pick-postal-code"]');
|
||||||
|
|
||||||
|
apps.forEach(function(el) {
|
||||||
|
|
||||||
|
const
|
||||||
|
uniqId = el.dataset.uniqid,
|
||||||
|
input = document.querySelector(`input[data-input-uniqid="${uniqId}"]`),
|
||||||
|
cityIdValue = input.value === '' ? null : input.value
|
||||||
|
;
|
||||||
|
|
||||||
|
if (cityIdValue !== null) {
|
||||||
|
makeFetch('GET', `/api/1.0/main/postal-code/${cityIdValue}.json`).then(city => {
|
||||||
|
loadOnePicker(el, input, uniqId, city);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
loadOnePicker(el, input, uniqId, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function(e) {
|
||||||
|
loadDynamicPickers(document)
|
||||||
|
})
|
@ -0,0 +1,29 @@
|
|||||||
|
# Pickpostalcode
|
||||||
|
|
||||||
|
Allow to pick a postal code.
|
||||||
|
|
||||||
|
In use with module `mod_pick_postal_code`, associated with `PickPostalCodeType` in php.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`<pick-postal-code @select-city="onCitySelected" @removeCity="onCityRemoved" :picked="city"></pick-postal-code>`
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
* `picked`: the city picked. A javascript object (a city). Null if empty.
|
||||||
|
* `country`: country to restraint search on picked. May be null.
|
||||||
|
|
||||||
|
## Emits
|
||||||
|
|
||||||
|
### `selectCity`
|
||||||
|
|
||||||
|
When a city is onCitySelected.
|
||||||
|
|
||||||
|
Argument: a js object, representing a city
|
||||||
|
|
||||||
|
### `removeCity`
|
||||||
|
|
||||||
|
When a city is removed.
|
||||||
|
|
||||||
|
|
||||||
|
Argument: a js object, representing a city
|
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div class="PickPostalCode">
|
||||||
|
<vue-multiselect
|
||||||
|
id="citySelector"
|
||||||
|
@search-change="listenInputSearch"
|
||||||
|
ref="citySelector"
|
||||||
|
v-model="internalPicked"
|
||||||
|
@select="selectCity"
|
||||||
|
@remove="remove"
|
||||||
|
name=""
|
||||||
|
track-by="id"
|
||||||
|
label="value"
|
||||||
|
:custom-label="transName"
|
||||||
|
:placeholder="$t('select_city')"
|
||||||
|
:select-label="$t('multiselect.select_label')"
|
||||||
|
:deselect-label="$t('multiselect.deselect_label')"
|
||||||
|
:selected-label="$t('multiselect.selected_label')"
|
||||||
|
:taggable="true"
|
||||||
|
:multiple="false"
|
||||||
|
:internal-search="false"
|
||||||
|
:loading="isLoading"
|
||||||
|
:options="cities"></vue-multiselect>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
|
||||||
|
import VueMultiselect from "vue-multiselect";
|
||||||
|
import {reactive, defineProps, onMounted} from "vue";
|
||||||
|
import {fetchCities, searchCities} from "./api";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
VueMultiselect,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
cities: [],
|
||||||
|
internalPicked: null,
|
||||||
|
isLoading: false,
|
||||||
|
abortControllers: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['pickCity', 'removeCity'],
|
||||||
|
props: {
|
||||||
|
picked: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.picked !== null) {
|
||||||
|
this.internalPicked = this.picked;
|
||||||
|
this.cities.push(this.picked);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
transName(value) {
|
||||||
|
return (value.code && value.name) ? `${value.name} (${value.code})` : '';
|
||||||
|
},
|
||||||
|
selectCity(city) {
|
||||||
|
this.$emit('selectCity', city);
|
||||||
|
},
|
||||||
|
listenInputSearch(query) {
|
||||||
|
if (query.length <= 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = this.abortControllers.pop();
|
||||||
|
|
||||||
|
while (typeof c !== 'undefined') {
|
||||||
|
c.abort();
|
||||||
|
c = this.abortControllers.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
let controller = new AbortController();
|
||||||
|
this.abortControllers.push(controller);
|
||||||
|
|
||||||
|
searchCities(query, this.country, controller).then(
|
||||||
|
newCities => {
|
||||||
|
this.cities = this.cities.filter(city => city.id === this.picked);
|
||||||
|
newCities.forEach(item => {
|
||||||
|
this.cities.push(item);
|
||||||
|
})
|
||||||
|
this.isLoading = false;
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error); //TODO better error handling
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
remove(item) {
|
||||||
|
this.$emit('removeCity', item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,3 @@
|
|||||||
|
.PickPostalCode {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import {makeFetch, fetchResults} from 'ChillMainAssets/lib/api/apiMethods';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint chill_api_single_postal_code__index
|
||||||
|
* method GET, get Cities Object
|
||||||
|
* @params {object} a country object
|
||||||
|
* @returns {Promise} a promise containing all Postal Code objects filtered with country
|
||||||
|
*/
|
||||||
|
const fetchCities = (country) => {
|
||||||
|
// warning: do not use fetchResults (in apiMethods): we need only a **part** of the results in the db
|
||||||
|
const params = new URLSearchParams({item_per_page: 100});
|
||||||
|
|
||||||
|
if (country !== null) {
|
||||||
|
params.append('country', country.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeFetch('GET', `/api/1.0/main/postal-code.json?${params.toString()}`).then(r => Promise.resolve(r.results));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint chill_main_postalcodeapi_search
|
||||||
|
* method GET, get Cities Object
|
||||||
|
* @params {string} search a search string
|
||||||
|
* @params {object} country a country object
|
||||||
|
* @params {AbortController} an abort controller
|
||||||
|
* @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string
|
||||||
|
*/
|
||||||
|
const searchCities = (search, country, controller) => {
|
||||||
|
const url = '/api/1.0/main/postal-code/search.json?';
|
||||||
|
const params = new URLSearchParams({q: search});
|
||||||
|
|
||||||
|
if (country !== null) {
|
||||||
|
Object.assign('country', country.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeFetch('GET', url + params, null, {signal: controller.signal})
|
||||||
|
.then(result => Promise.resolve(result.results));
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
fetchCities,
|
||||||
|
searchCities,
|
||||||
|
};
|
@ -251,3 +251,9 @@
|
|||||||
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value|escape('html_attr') }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
|
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value|escape('html_attr') }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
|
||||||
<div data-module="pick-dynamic" data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}"></div>
|
<div data-module="pick-dynamic" data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block pick_postal_code_widget %}
|
||||||
|
{{ form_help(form)}}
|
||||||
|
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
|
||||||
|
<div data-module="pick-postal-code" data-uniqid="{{ form.vars['uniqid'] }}"></div>
|
||||||
|
{% endblock %}
|
||||||
|
@ -95,6 +95,15 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if entity_workflow.currentStep.destEmail|length > 0 %}
|
||||||
|
<p><b>{{ 'workflow.An access key was also sent to those addresses'|trans }} :</b></p>
|
||||||
|
<ul>
|
||||||
|
{% for e in entity_workflow.currentStep.destEmail -%}
|
||||||
|
<li><a href="mailto:{{ e|escape('html_attr') }}">{{ e }}</a></li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
|
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
|
||||||
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
||||||
<ul>
|
<ul>
|
||||||
@ -103,6 +112,21 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if is_granted('CHILL_MAIN_WORKFLOW_LINK_SHOW', entity_workflow) %}
|
||||||
|
<p><b>{{ 'workflow.This link grant any user to apply a transition'|trans }} :</b></p>
|
||||||
|
|
||||||
|
{% set link = absolute_url(path('chill_main_workflow_grant_access_by_key', {'id': entity_workflow.currentStep.id, 'accessKey': entity_workflow.currentStep.accessKey})) %}
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" readonly value="{{ link|e('html_attr') }}" class="form-control">
|
||||||
|
<button class="btn btn-secondary" type="button" id="button-copy" onclick="navigator.clipboard.writeText('{{ link|e('html_attr') }}').then(() => { window.alert({{ ('"' ~ 'workflow.Access link copied'|trans ~ ' !"') |e('html_attr') }})});"><i class="fa fa-files-o"></i></button>
|
||||||
|
<a class="btn btn-secondary" type="button" id="button-email"
|
||||||
|
href="mailto:?body={{ ((('workflow.The workflow may be accssed through this link'|trans)~':')|e('url')) ~ '%0D%0A%0D%0A' ~ link|e('url') }}"><i class="fa fa-envelope"></i></a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -81,6 +81,15 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if entity_workflow.currentStep.destEmail|length > 0 %}
|
||||||
|
<p><b>{{ 'workflow.An access key was also sent to those addresses'|trans }} :</b></p>
|
||||||
|
<ul>
|
||||||
|
{% for e in entity_workflow.currentStep.destEmail -%}
|
||||||
|
<li><a href="mailto:{{ e|escape('html_attr') }}">{{ e }}</a></li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if step.destUserByAccessKey|length > 0 %}
|
{% if step.destUserByAccessKey|length > 0 %}
|
||||||
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -190,8 +190,6 @@ class AuthorizationHelper implements AuthorizationHelperInterface
|
|||||||
/**
|
/**
|
||||||
* Return all reachable scope for a given user, center and role.
|
* Return all reachable scope for a given user, center and role.
|
||||||
*
|
*
|
||||||
* @deprecated Use getReachableCircles
|
|
||||||
*
|
|
||||||
* @param Center|Center[] $center
|
* @param Center|Center[] $center
|
||||||
*
|
*
|
||||||
* @return array|Scope[]
|
* @return array|Scope[]
|
||||||
|
@ -27,6 +27,8 @@ class EntityWorkflowVoter extends Voter
|
|||||||
|
|
||||||
public const SEE = 'CHILL_MAIN_WORKFLOW_SEE';
|
public const SEE = 'CHILL_MAIN_WORKFLOW_SEE';
|
||||||
|
|
||||||
|
public const SHOW_ENTITY_LINK = 'CHILL_MAIN_WORKFLOW_LINK_SHOW';
|
||||||
|
|
||||||
private EntityWorkflowManager $manager;
|
private EntityWorkflowManager $manager;
|
||||||
|
|
||||||
private Security $security;
|
private Security $security;
|
||||||
@ -80,6 +82,19 @@ class EntityWorkflowVoter extends Voter
|
|||||||
case self::DELETE:
|
case self::DELETE:
|
||||||
return $subject->getStep() === 'initial';
|
return $subject->getStep() === 'initial';
|
||||||
|
|
||||||
|
case self::SHOW_ENTITY_LINK:
|
||||||
|
if ($subject->getStep() === 'initial') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentStep = $subject->getCurrentStepChained();
|
||||||
|
|
||||||
|
if ($currentStep->isFinal()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $currentStep->getPrevious()->getTransitionBy() === $this->security->getUser();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new UnexpectedValueException("attribute {$attribute} not supported");
|
throw new UnexpectedValueException("attribute {$attribute} not supported");
|
||||||
}
|
}
|
||||||
@ -91,6 +106,7 @@ class EntityWorkflowVoter extends Voter
|
|||||||
self::SEE,
|
self::SEE,
|
||||||
self::CREATE,
|
self::CREATE,
|
||||||
self::DELETE,
|
self::DELETE,
|
||||||
|
self::SHOW_ENTITY_LINK,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Service\Import;
|
namespace Chill\MainBundle\Service\Import;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use League\Csv\Reader;
|
use League\Csv\Reader;
|
||||||
use League\Csv\Statement;
|
use League\Csv\Statement;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
@ -17,6 +17,7 @@ namespace Chill\MainBundle\Test;
|
|||||||
* **Usage : ** You must set up trait with `setUpTrait` before use
|
* **Usage : ** You must set up trait with `setUpTrait` before use
|
||||||
* and use tearDownTrait after usage.
|
* and use tearDownTrait after usage.
|
||||||
*
|
*
|
||||||
|
* @deprecated use @see{\Prophecy\PhpUnit\ProphecyTrait} instead
|
||||||
* @codeCoverageIgnore
|
* @codeCoverageIgnore
|
||||||
*
|
*
|
||||||
* @deprecated use @class{Prophecy\PhpUnit\ProphecyTrait} instead
|
* @deprecated use @class{Prophecy\PhpUnit\ProphecyTrait} instead
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
<?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 Doctrine\DQL;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Country;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class JsonExtractTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
|
||||||
|
$this->em = self::$container->get(EntityManagerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dataGenerateDql(): iterable
|
||||||
|
{
|
||||||
|
yield ['SELECT JSON_EXTRACT(c.name, \'fr\') FROM ' . Country::class . ' c', []];
|
||||||
|
|
||||||
|
yield ['SELECT JSON_EXTRACT(c.name, :lang) FROM ' . Country::class . ' c', ['lang' => 'fr']];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dataGenerateDql
|
||||||
|
*/
|
||||||
|
public function testJsonExtract(string $dql, array $args)
|
||||||
|
{
|
||||||
|
$results = $this->em->createQuery($dql)
|
||||||
|
->setMaxResults(2)
|
||||||
|
->setParameters($args)
|
||||||
|
->getResult();
|
||||||
|
|
||||||
|
$this->assertIsArray($results, 'simply test that the query return a result');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
<?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 Form\Type;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Form\Type\DataTransformer\PostalCodeToIdTransformer;
|
||||||
|
use Chill\MainBundle\Form\Type\PickPostalCodeType;
|
||||||
|
use Chill\MainBundle\Repository\PostalCodeRepositoryInterface;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use ReflectionClass;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
|
use Symfony\Component\Form\PreloadedExtension;
|
||||||
|
use Symfony\Component\Form\Test\TypeTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class PickPostalCodeTypeTest extends TypeTestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
public function testSubmitValidData(): void
|
||||||
|
{
|
||||||
|
$builder = $this->factory->createBuilder(FormType::class, ['postal_code' => null]);
|
||||||
|
$builder->add('postal_code', PickPostalCodeType::class);
|
||||||
|
$form = $builder->getForm();
|
||||||
|
|
||||||
|
$form->submit(['postal_code' => '1']);
|
||||||
|
|
||||||
|
$this->assertTrue($form->isSynchronized());
|
||||||
|
|
||||||
|
$this->assertEquals(1, $form['postal_code']->getData()->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getExtensions()
|
||||||
|
{
|
||||||
|
$postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class);
|
||||||
|
$postalCodeRepository->find(Argument::any())
|
||||||
|
->will(static function ($args) {
|
||||||
|
$postalCode = new PostalCode();
|
||||||
|
$reflectionClass = new ReflectionClass($postalCode);
|
||||||
|
$id = $reflectionClass->getProperty('id');
|
||||||
|
$id->setAccessible(true);
|
||||||
|
$id->setValue($postalCode, (int) $args[0]);
|
||||||
|
|
||||||
|
return $postalCode;
|
||||||
|
});
|
||||||
|
|
||||||
|
$type = new PickPostalCodeType(
|
||||||
|
new PostalCodeToIdTransformer(
|
||||||
|
$postalCodeRepository->reveal()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
new PreloadedExtension([$type], []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -72,7 +72,7 @@ final class ValidPhonenumber extends ConstraintValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (false === $isValid) {
|
if (false === $isValid) {
|
||||||
$this->context->addViolation($message, ['%phonenumber%' => $value]);
|
$this->context->addViolation($message, ['%phonenumber%' => $value, '%formatted%' => $this->phonenumberHelper->format($value)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,7 @@ module.exports = function(encore, entries)
|
|||||||
encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js');
|
encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js');
|
||||||
encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js');
|
encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js');
|
||||||
encore.addEntry('mod_wopi_link', __dirname + '/Resources/public/module/wopi-link/index.js');
|
encore.addEntry('mod_wopi_link', __dirname + '/Resources/public/module/wopi-link/index.js');
|
||||||
|
encore.addEntry('mod_pick_postal_code', __dirname + '/Resources/public/module/pick-postal-code/index.js');
|
||||||
|
|
||||||
// Vue entrypoints
|
// Vue entrypoints
|
||||||
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
|
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
|
||||||
|
@ -454,7 +454,6 @@ workflow:
|
|||||||
Delete workflow: Supprimer le workflow
|
Delete workflow: Supprimer le workflow
|
||||||
Steps is not waiting for transition. Maybe someone apply the transition before you ?: L'étape que vous cherchez a déjà été modifiée par un autre utilisateur. Peut-être quelqu'un a-t-il modifié cette étape avant vous ?
|
Steps is not waiting for transition. Maybe someone apply the transition before you ?: L'étape que vous cherchez a déjà été modifiée par un autre utilisateur. Peut-être quelqu'un a-t-il modifié cette étape avant vous ?
|
||||||
You get access to this step: Vous avez acquis les droits pour appliquer une transition sur ce workflow.
|
You get access to this step: Vous avez acquis les droits pour appliquer une transition sur ce workflow.
|
||||||
Those users are also granted to apply a transition by using an access key: Ces utilisateurs peuvent également valider cette étape, grâce à un lien d'accès
|
|
||||||
dest by email: Liens d'autorisation par email
|
dest by email: Liens d'autorisation par email
|
||||||
dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Ce lien d'accès permettra à l'utilisateur de valider cette étape.
|
dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Ce lien d'accès permettra à l'utilisateur de valider cette étape.
|
||||||
Add an email: Ajouter une adresse email
|
Add an email: Ajouter une adresse email
|
||||||
@ -466,6 +465,11 @@ workflow:
|
|||||||
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
|
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
|
||||||
For: Pour
|
For: Pour
|
||||||
You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire.
|
You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire.
|
||||||
|
An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces addresses
|
||||||
|
Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtennu l'accès grâce au lien reçu par email
|
||||||
|
Access link copied: Lien d'accès copié
|
||||||
|
This link grant any user to apply a transition: Le lien d'accès suivant permet d'appliquer une transition
|
||||||
|
The workflow may be accssed through this link: Une transition peut être appliquée sur ce workflow grâce au lien d'accès suivant
|
||||||
|
|
||||||
|
|
||||||
Subscribe final: Recevoir une notification à l'étape finale
|
Subscribe final: Recevoir une notification à l'étape finale
|
||||||
|
@ -62,6 +62,7 @@ class UserRefEventSubscriber implements EventSubscriberInterface
|
|||||||
&& $period->getUser() !== $this->security->getUser()
|
&& $period->getUser() !== $this->security->getUser()
|
||||||
&& null !== $period->getUser()
|
&& null !== $period->getUser()
|
||||||
&& $period->getStep() !== AccompanyingPeriod::STEP_DRAFT
|
&& $period->getStep() !== AccompanyingPeriod::STEP_DRAFT
|
||||||
|
&& !$period->isPreventUserIsChangedNotification()
|
||||||
) {
|
) {
|
||||||
$this->generateNotificationToUser($period);
|
$this->generateNotificationToUser($period);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\Controller;
|
namespace Chill\PersonBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Form\Type\PickPostalCodeType;
|
||||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Repository\UserRepository;
|
use Chill\MainBundle\Repository\UserRepository;
|
||||||
@ -92,12 +94,14 @@ class ReassignAccompanyingPeriodController extends AbstractController
|
|||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
$userFrom = $form['user']->getData();
|
$userFrom = $form['user']->getData();
|
||||||
|
$postalCodes = $form['postal_code']->getData() instanceof PostalCode ? [$form['postal_code']->getData()] : [];
|
||||||
|
|
||||||
$total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod($userFrom);
|
$total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod($userFrom);
|
||||||
$paginator = $this->paginatorFactory->create($total);
|
$paginator = $this->paginatorFactory->create($total);
|
||||||
$periods = $this->accompanyingPeriodACLAwareRepository
|
$periods = $this->accompanyingPeriodACLAwareRepository
|
||||||
->findByUserOpenedAccompanyingPeriod(
|
->findByUserAndPostalCodesOpenedAccompanyingPeriod(
|
||||||
$userFrom,
|
$userFrom,
|
||||||
|
$postalCodes,
|
||||||
['openingDate' => 'ASC'],
|
['openingDate' => 'ASC'],
|
||||||
$paginator->getItemsPerPage(),
|
$paginator->getItemsPerPage(),
|
||||||
$paginator->getCurrentPageFirstItemNumber()
|
$paginator->getCurrentPageFirstItemNumber()
|
||||||
@ -123,7 +127,7 @@ class ReassignAccompanyingPeriodController extends AbstractController
|
|||||||
$period = $this->courseRepository->find($periodId);
|
$period = $this->courseRepository->find($periodId);
|
||||||
|
|
||||||
if ($period->getUser() === $userFrom) {
|
if ($period->getUser() === $userFrom) {
|
||||||
$period->setUser($userTo);
|
$period->setUser($userTo, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +152,9 @@ class ReassignAccompanyingPeriodController extends AbstractController
|
|||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
'user' => null,
|
'user' => null,
|
||||||
|
'postal_code' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
$builder = $this->formFactory->createBuilder(FormType::class, $data, [
|
$builder = $this->formFactory->createBuilder(FormType::class, $data, [
|
||||||
'method' => 'get', 'csrf_protection' => false, ]);
|
'method' => 'get', 'csrf_protection' => false, ]);
|
||||||
|
|
||||||
@ -158,12 +164,17 @@ class ReassignAccompanyingPeriodController extends AbstractController
|
|||||||
'label' => 'reassign.Current user',
|
'label' => 'reassign.Current user',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'help' => 'reassign.Choose a user and click on "Filter" to apply',
|
'help' => 'reassign.Choose a user and click on "Filter" to apply',
|
||||||
|
])
|
||||||
|
->add('postal_code', PickPostalCodeType::class, [
|
||||||
|
'label' => 'reassign.Filter by postal code',
|
||||||
|
'required' => false,
|
||||||
|
'help' => 'reassign.Filter course which are located inside a postal code',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $builder->getForm();
|
return $builder->getForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildReassignForm(array $periodIds, ?User $userFrom): FormInterface
|
private function buildReassignForm(array $periodIds, ?User $userFrom = null): FormInterface
|
||||||
{
|
{
|
||||||
$defaultData = [
|
$defaultData = [
|
||||||
'userFrom' => $userFrom,
|
'userFrom' => $userFrom,
|
||||||
|
@ -270,6 +270,8 @@ class AccompanyingPeriod implements
|
|||||||
*/
|
*/
|
||||||
private ?Comment $pinnedComment = null;
|
private ?Comment $pinnedComment = null;
|
||||||
|
|
||||||
|
private bool $preventUserIsChangedNotification = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="text")
|
* @ORM\Column(type="text")
|
||||||
* @Groups({"read", "write"})
|
* @Groups({"read", "write"})
|
||||||
@ -1077,6 +1079,11 @@ class AccompanyingPeriod implements
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isPreventUserIsChangedNotification(): bool
|
||||||
|
{
|
||||||
|
return $this->preventUserIsChangedNotification;
|
||||||
|
}
|
||||||
|
|
||||||
public function isRequestorAnonymous(): bool
|
public function isRequestorAnonymous(): bool
|
||||||
{
|
{
|
||||||
return $this->requestorAnonymous;
|
return $this->requestorAnonymous;
|
||||||
@ -1372,11 +1379,12 @@ class AccompanyingPeriod implements
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUser(?User $user): self
|
public function setUser(?User $user, bool $preventNotification = false): self
|
||||||
{
|
{
|
||||||
if ($this->user !== $user) {
|
if ($this->user !== $user) {
|
||||||
$this->userPrevious = $this->user;
|
$this->userPrevious = $this->user;
|
||||||
$this->userIsChanged = true;
|
$this->userIsChanged = true;
|
||||||
|
$this->preventUserIsChangedNotification = $preventNotification;
|
||||||
|
|
||||||
foreach ($this->userHistories as $history) {
|
foreach ($this->userHistories as $history) {
|
||||||
if (null === $history->getEndDate()) {
|
if (null === $history->getEndDate()) {
|
||||||
|
@ -35,6 +35,11 @@ class MaritalStatus
|
|||||||
*/
|
*/
|
||||||
private array $name;
|
private array $name;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->id = substr(md5(uniqid()), 0, 7);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get id.
|
* Get id.
|
||||||
*/
|
*/
|
||||||
|
@ -72,6 +72,9 @@ class Evaluation
|
|||||||
$this->socialActions = new ArrayCollection();
|
$this->socialActions = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal do use @see{SocialAction::addEvaluation}
|
||||||
|
*/
|
||||||
public function addSocialAction(SocialAction $socialAction): self
|
public function addSocialAction(SocialAction $socialAction): self
|
||||||
{
|
{
|
||||||
if (!$this->socialActions->contains($socialAction)) {
|
if (!$this->socialActions->contains($socialAction)) {
|
||||||
@ -111,6 +114,11 @@ class Evaluation
|
|||||||
return $this->url;
|
return $this->url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @internal do use @see{SocialAction::removeEvaluation}
|
||||||
|
*/
|
||||||
public function removeSocialAction(SocialAction $socialAction): self
|
public function removeSocialAction(SocialAction $socialAction): self
|
||||||
{
|
{
|
||||||
if ($this->socialActions->contains($socialAction)) {
|
if ($this->socialActions->contains($socialAction)) {
|
||||||
|
@ -112,6 +112,7 @@ class SocialAction
|
|||||||
{
|
{
|
||||||
if (!$this->evaluations->contains($evaluation)) {
|
if (!$this->evaluations->contains($evaluation)) {
|
||||||
$this->evaluations[] = $evaluation;
|
$this->evaluations[] = $evaluation;
|
||||||
|
$evaluation->addSocialAction($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -310,6 +311,7 @@ class SocialAction
|
|||||||
public function removeEvaluation(Evaluation $evaluation): self
|
public function removeEvaluation(Evaluation $evaluation): self
|
||||||
{
|
{
|
||||||
$this->evaluations->removeElement($evaluation);
|
$this->evaluations->removeElement($evaluation);
|
||||||
|
$evaluation->removeSocialAction($this);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ namespace Chill\PersonBundle\Form;
|
|||||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||||
use Chill\PersonBundle\Entity\MaritalStatus;
|
use Chill\PersonBundle\Entity\MaritalStatus;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
@ -26,9 +25,6 @@ class MaritalStatusType extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('id', TextType::class, [
|
|
||||||
'label' => 'Identifiant',
|
|
||||||
])
|
|
||||||
->add('name', TranslatableStringFormType::class, [
|
->add('name', TranslatableStringFormType::class, [
|
||||||
'label' => 'Nom',
|
'label' => 'Nom',
|
||||||
]);
|
]);
|
||||||
|
@ -11,7 +11,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\Repository;
|
namespace Chill\PersonBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Address;
|
||||||
use Chill\MainBundle\Entity\Location;
|
use Chill\MainBundle\Entity\Location;
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Entity\UserJob;
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
@ -19,10 +21,14 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
|||||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||||
|
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Query\Expr\Join;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
use function count;
|
use function count;
|
||||||
@ -49,7 +55,12 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
|
|||||||
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildQueryOpenedAccompanyingCourseByUser(?User $user)
|
/**
|
||||||
|
* @param array|PostalCode[]
|
||||||
|
*
|
||||||
|
* @return QueryBuilder
|
||||||
|
*/
|
||||||
|
public function buildQueryOpenedAccompanyingCourseByUser(?User $user, array $postalCodes = [])
|
||||||
{
|
{
|
||||||
$qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap');
|
$qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap');
|
||||||
|
|
||||||
@ -65,6 +76,37 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
|
|||||||
->setParameter('now', new DateTime('now'))
|
->setParameter('now', new DateTime('now'))
|
||||||
->setParameter('draft', AccompanyingPeriod::STEP_DRAFT);
|
->setParameter('draft', AccompanyingPeriod::STEP_DRAFT);
|
||||||
|
|
||||||
|
if ([] !== $postalCodes) {
|
||||||
|
$qb->join('ap.locationHistories', 'location_history')
|
||||||
|
->leftJoin(PersonHouseholdAddress::class, 'person_address', Join::WITH, 'IDENTITY(location_history.personLocation) = IDENTITY(person_address.person)')
|
||||||
|
->join(
|
||||||
|
Address::class,
|
||||||
|
'address',
|
||||||
|
Join::WITH,
|
||||||
|
'COALESCE(IDENTITY(location_history.addressLocation), IDENTITY(person_address.address)) = address.id'
|
||||||
|
)
|
||||||
|
->andWhere(
|
||||||
|
$qb->expr()->orX(
|
||||||
|
$qb->expr()->isNull('person_address'),
|
||||||
|
$qb->expr()->andX(
|
||||||
|
$qb->expr()->lte('person_address.validFrom', ':now'),
|
||||||
|
$qb->expr()->orX(
|
||||||
|
$qb->expr()->isNull('person_address.validTo'),
|
||||||
|
$qb->expr()->lt('person_address.validTo', ':now')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->andWhere(
|
||||||
|
$qb->expr()->isNull('location_history.endDate')
|
||||||
|
)
|
||||||
|
->andWhere(
|
||||||
|
$qb->expr()->in('address.postcode', ':postal_codes')
|
||||||
|
)
|
||||||
|
->setParameter('now', new DateTimeImmutable('now'), Types::DATE_IMMUTABLE)
|
||||||
|
->setParameter('postal_codes', $postalCodes);
|
||||||
|
}
|
||||||
|
|
||||||
return $qb;
|
return $qb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +119,18 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
|
|||||||
return $qb->getQuery()->getSingleScalarResult();
|
return $qb->getQuery()->getSingleScalarResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes): int
|
||||||
|
{
|
||||||
|
if (null === $user) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->buildQueryOpenedAccompanyingCourseByUser($user, $postalCodes)
|
||||||
|
->select('COUNT(ap)')
|
||||||
|
->getQuery()
|
||||||
|
->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
public function countByUserOpenedAccompanyingPeriod(?User $user): int
|
public function countByUserOpenedAccompanyingPeriod(?User $user): int
|
||||||
{
|
{
|
||||||
if (null === $user) {
|
if (null === $user) {
|
||||||
@ -158,6 +212,24 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
|
|||||||
return $qb->getQuery()->getResult();
|
return $qb->getQuery()->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes, array $orderBy = [], int $limit = 0, int $offset = 50): array
|
||||||
|
{
|
||||||
|
if (null === $user) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb = $this->buildQueryOpenedAccompanyingCourseByUser($user);
|
||||||
|
|
||||||
|
$qb->setFirstResult($offset)
|
||||||
|
->setMaxResults($limit);
|
||||||
|
|
||||||
|
foreach ($orderBy as $field => $direction) {
|
||||||
|
$qb->addOrderBy('ap.' . $field, $direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $qb->getQuery()->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array|AccompanyingPeriod[]
|
* @return array|AccompanyingPeriod[]
|
||||||
*/
|
*/
|
||||||
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\Repository;
|
namespace Chill\PersonBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Entity\UserJob;
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
@ -25,6 +26,11 @@ interface AccompanyingPeriodACLAwareRepositoryInterface
|
|||||||
*/
|
*/
|
||||||
public function countByUnDispatched(array $jobs, array $services, array $administrativeLocations): int;
|
public function countByUnDispatched(array $jobs, array $services, array $administrativeLocations): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|PostalCode[] $postalCodes
|
||||||
|
*/
|
||||||
|
public function countByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes): int;
|
||||||
|
|
||||||
public function countByUserOpenedAccompanyingPeriod(?User $user): int;
|
public function countByUserOpenedAccompanyingPeriod(?User $user): int;
|
||||||
|
|
||||||
public function findByPerson(
|
public function findByPerson(
|
||||||
@ -43,5 +49,10 @@ interface AccompanyingPeriodACLAwareRepositoryInterface
|
|||||||
*/
|
*/
|
||||||
public function findByUnDispatched(array $jobs, array $services, array $administrativeLocations, ?int $limit = null, ?int $offset = null): array;
|
public function findByUnDispatched(array $jobs, array $services, array $administrativeLocations, ?int $limit = null, ?int $offset = null): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|PostalCode[] $postalCodes
|
||||||
|
*/
|
||||||
|
public function findByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes, array $orderBy = [], int $limit = 0, int $offset = 50): array;
|
||||||
|
|
||||||
public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): array;
|
public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): array;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Repository\SocialWork;
|
|||||||
|
|
||||||
use Chill\PersonBundle\Entity\SocialWork\Goal;
|
use Chill\PersonBundle\Entity\SocialWork\Goal;
|
||||||
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
|
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
|
||||||
|
use DateTime;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
@ -73,6 +74,14 @@ final class GoalRepository implements ObjectRepository
|
|||||||
$qb = $this->buildQueryBySocialActionWithDescendants($action);
|
$qb = $this->buildQueryBySocialActionWithDescendants($action);
|
||||||
$qb->select('g');
|
$qb->select('g');
|
||||||
|
|
||||||
|
$qb->andWhere(
|
||||||
|
$qb->expr()->orX(
|
||||||
|
$qb->expr()->isNull('g.desactivationDate'),
|
||||||
|
$qb->expr()->gt('g.desactivationDate', ':now')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->setParameter('now', new DateTime('now'));
|
||||||
|
|
||||||
foreach ($orderBy as $sort => $order) {
|
foreach ($orderBy as $sort => $order) {
|
||||||
$qb->addOrderBy('g.' . $sort, $order);
|
$qb->addOrderBy('g.' . $sort, $order);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
<div class="flex-table">
|
<div class="flex-table">
|
||||||
{% for period in periods %}
|
{% for period in periods %}
|
||||||
{% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period,
|
{% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period,
|
||||||
'recordAction': m.period_actions(period), 'itemMeta': m.period_meta(period) } %}
|
'recordAction': m.period_actions(period), 'itemMeta': m.period_meta(period), 'show_address': true } %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -113,6 +113,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if show_address|default(false) and period.location is not null %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title"><h3>{{ 'Accompanying course location'|trans }}</h3></div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<p class="wl-item">
|
||||||
|
{{ period.location|chill_entity_render_string }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -5,11 +5,13 @@
|
|||||||
{% block js %}
|
{% block js %}
|
||||||
{{ encore_entry_script_tags('mod_set_referrer') }}
|
{{ encore_entry_script_tags('mod_set_referrer') }}
|
||||||
{{ encore_entry_script_tags('mod_pickentity_type') }}
|
{{ encore_entry_script_tags('mod_pickentity_type') }}
|
||||||
|
{{ encore_entry_script_tags('mod_pick_postal_code') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ encore_entry_link_tags('mod_set_referrer') }}
|
{{ encore_entry_link_tags('mod_set_referrer') }}
|
||||||
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
||||||
|
{{ encore_entry_link_tags('mod_pick_postal_code') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% macro period_meta(period) %}
|
{% macro period_meta(period) %}
|
||||||
@ -48,6 +50,8 @@
|
|||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
{{ form_label(form.user ) }}
|
{{ form_label(form.user ) }}
|
||||||
{{ form_widget(form.user, {'attr': {'class': 'select2'}}) }}
|
{{ form_widget(form.user, {'attr': {'class': 'select2'}}) }}
|
||||||
|
{{ form_label(form.postal_code) }}
|
||||||
|
{{ form_widget(form.postal_code) }}
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<button type="submit" class="btn btn-misc">
|
<button type="submit" class="btn btn-misc">
|
||||||
@ -87,7 +91,7 @@
|
|||||||
<div class="flex-table">
|
<div class="flex-table">
|
||||||
{% for period in periods %}
|
{% for period in periods %}
|
||||||
{% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period,
|
{% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period,
|
||||||
'recordAction': m.period_actions(period), 'itemMeta': m.period_meta(period) } %}
|
'recordAction': m.period_actions(period), 'itemMeta': m.period_meta(period), 'show_address': true } %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if userFrom is same as(null) %}
|
{% if userFrom is same as(null) %}
|
||||||
<p class="chill-no-data-statement">{{ 'period_by_user_list.Pick a user'|trans }}</p>
|
<p class="chill-no-data-statement">{{ 'period_by_user_list.Pick a user'|trans }}</p>
|
||||||
|
@ -11,7 +11,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\Service\DocGenerator;
|
namespace Chill\PersonBundle\Service\DocGenerator;
|
||||||
|
|
||||||
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
|
|
||||||
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
|
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
|
||||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
|
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
|
||||||
@ -19,46 +18,73 @@ use Chill\DocStoreBundle\Entity\DocumentCategory;
|
|||||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
|
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
|
||||||
|
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Form\Type\ScopePickerType;
|
||||||
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||||
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use LogicException;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
use function array_key_exists;
|
use function array_key_exists;
|
||||||
|
use function count;
|
||||||
|
|
||||||
class PersonContext implements DocGeneratorContextWithAdminFormInterface
|
final class PersonContext implements PersonContextInterface
|
||||||
{
|
{
|
||||||
|
private AuthorizationHelperInterface $authorizationHelper;
|
||||||
|
|
||||||
private BaseContextData $baseContextData;
|
private BaseContextData $baseContextData;
|
||||||
|
|
||||||
|
private CenterResolverManagerInterface $centerResolverManager;
|
||||||
|
|
||||||
private DocumentCategoryRepository $documentCategoryRepository;
|
private DocumentCategoryRepository $documentCategoryRepository;
|
||||||
|
|
||||||
private EntityManagerInterface $em;
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
private NormalizerInterface $normalizer;
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
private bool $showScopes;
|
||||||
|
|
||||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
private TranslatorInterface $translator;
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
AuthorizationHelperInterface $authorizationHelper,
|
||||||
|
BaseContextData $baseContextData,
|
||||||
|
CenterResolverManagerInterface $centerResolverManager,
|
||||||
DocumentCategoryRepository $documentCategoryRepository,
|
DocumentCategoryRepository $documentCategoryRepository,
|
||||||
NormalizerInterface $normalizer,
|
|
||||||
TranslatableStringHelperInterface $translatableStringHelper,
|
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
|
NormalizerInterface $normalizer,
|
||||||
|
ParameterBagInterface $parameterBag,
|
||||||
|
Security $security,
|
||||||
TranslatorInterface $translator,
|
TranslatorInterface $translator,
|
||||||
BaseContextData $baseContextData
|
TranslatableStringHelperInterface $translatableStringHelper
|
||||||
) {
|
) {
|
||||||
$this->documentCategoryRepository = $documentCategoryRepository;
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
$this->normalizer = $normalizer;
|
$this->centerResolverManager = $centerResolverManager;
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
|
||||||
$this->em = $em;
|
|
||||||
$this->baseContextData = $baseContextData;
|
$this->baseContextData = $baseContextData;
|
||||||
|
$this->documentCategoryRepository = $documentCategoryRepository;
|
||||||
|
$this->em = $em;
|
||||||
|
$this->normalizer = $normalizer;
|
||||||
|
$this->security = $security;
|
||||||
|
$this->showScopes = $parameterBag->get('chill_main')['acl']['form_show_scopes'];
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function adminFormReverseTransform(array $data): array
|
public function adminFormReverseTransform(array $data): array
|
||||||
@ -102,9 +128,30 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface
|
|||||||
'choice_label' => function ($entity = null) {
|
'choice_label' => function ($entity = null) {
|
||||||
return $entity ? $this->translatableStringHelper->localize($entity->getName()) : '';
|
return $entity ? $this->translatableStringHelper->localize($entity->getName()) : '';
|
||||||
},
|
},
|
||||||
|
'required' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Person $entity
|
||||||
|
*/
|
||||||
|
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
|
||||||
|
{
|
||||||
|
$builder->add('title', TextType::class, [
|
||||||
|
'required' => true,
|
||||||
|
'label' => 'docgen.Document title',
|
||||||
|
'data' => $this->translatableStringHelper->localize($template->getName()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($this->isScopeNecessary($entity)) {
|
||||||
|
$builder->add('scope', ScopePickerType::class, [
|
||||||
|
'center' => $this->centerResolverManager->resolveCenters($entity),
|
||||||
|
'role' => PersonDocumentVoter::CREATE,
|
||||||
|
'label' => 'Scope',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
|
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
|
||||||
{
|
{
|
||||||
if (!$entity instanceof Person) {
|
if (!$entity instanceof Person) {
|
||||||
@ -156,6 +203,14 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Person $entity
|
||||||
|
*/
|
||||||
|
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Person $entity
|
* @param Person $entity
|
||||||
*/
|
*/
|
||||||
@ -163,7 +218,9 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface
|
|||||||
{
|
{
|
||||||
$doc = new PersonDocument();
|
$doc = new PersonDocument();
|
||||||
$doc->setTemplate($template)
|
$doc->setTemplate($template)
|
||||||
->setTitle($this->translatableStringHelper->localize($template->getName()))
|
->setTitle(
|
||||||
|
$contextGenerationData['title'] ?? $this->translatableStringHelper->localize($template->getName())
|
||||||
|
)
|
||||||
->setDate(new DateTime())
|
->setDate(new DateTime())
|
||||||
->setDescription($this->translatableStringHelper->localize($template->getName()))
|
->setDescription($this->translatableStringHelper->localize($template->getName()))
|
||||||
->setPerson($entity)
|
->setPerson($entity)
|
||||||
@ -178,6 +235,36 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->isScopeNecessary($entity)) {
|
||||||
|
$doc->setScope($contextGenerationData['scope']);
|
||||||
|
} elseif ($this->showScopes) {
|
||||||
|
// in this case, it should have only one scope possible, we get it through AuthorizationHelper::getReachableScopes
|
||||||
|
$scopes = $this->authorizationHelper->getReachableScopes(
|
||||||
|
$this->security->getUser(),
|
||||||
|
PersonDocumentVoter::CREATE,
|
||||||
|
$this->centerResolverManager->resolveCenters($entity)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (1 !== count($scopes)) {
|
||||||
|
throw new LogicException('at this step, it should have only one scope');
|
||||||
|
}
|
||||||
|
|
||||||
|
$doc->setScope($scopes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
$this->em->persist($doc);
|
$this->em->persist($doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isScopeNecessary(Person $person): bool
|
||||||
|
{
|
||||||
|
if ($this->showScopes && 1 < $this->authorizationHelper->getReachableScopes(
|
||||||
|
$this->security->getUser(),
|
||||||
|
PersonDocumentVoter::CREATE,
|
||||||
|
$this->centerResolverManager->resolveCenters($person)
|
||||||
|
)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
<?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\PersonBundle\Service\DocGenerator;
|
||||||
|
|
||||||
|
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
|
||||||
|
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
||||||
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
interface PersonContextInterface extends DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface
|
||||||
|
{
|
||||||
|
public function adminFormReverseTransform(array $data): array;
|
||||||
|
|
||||||
|
public function adminFormTransform(array $data): array;
|
||||||
|
|
||||||
|
public function buildAdminForm(FormBuilderInterface $builder): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Person $entity
|
||||||
|
*/
|
||||||
|
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void;
|
||||||
|
|
||||||
|
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array;
|
||||||
|
|
||||||
|
public function getDescription(): string;
|
||||||
|
|
||||||
|
public function getEntityClass(): string;
|
||||||
|
|
||||||
|
public function getFormData(DocGeneratorTemplate $template, $entity): array;
|
||||||
|
|
||||||
|
public function getName(): string;
|
||||||
|
|
||||||
|
public function hasAdminForm(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Person $entity
|
||||||
|
*/
|
||||||
|
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Person $entity
|
||||||
|
*/
|
||||||
|
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void;
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
<?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\PersonBundle\Service\DocGenerator;
|
||||||
|
|
||||||
|
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
|
||||||
|
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
||||||
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context to generate a document with a destinee (i.e. generate a letter).
|
||||||
|
*/
|
||||||
|
class PersonContextWithThirdParty implements DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface
|
||||||
|
{
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
private PersonContextInterface $personContext;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
PersonContextInterface $personContext,
|
||||||
|
NormalizerInterface $normalizer
|
||||||
|
) {
|
||||||
|
$this->personContext = $personContext;
|
||||||
|
$this->normalizer = $normalizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function adminFormReverseTransform(array $data): array
|
||||||
|
{
|
||||||
|
return array_merge(
|
||||||
|
$this->personContext->adminFormReverseTransform($data),
|
||||||
|
['label' => $data['label']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function adminFormTransform(array $data): array
|
||||||
|
{
|
||||||
|
return array_merge(
|
||||||
|
$this->personContext->adminFormTransform($data),
|
||||||
|
['label' => $data['label'] ?? '']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildAdminForm(FormBuilderInterface $builder): void
|
||||||
|
{
|
||||||
|
$this->personContext->buildAdminForm($builder);
|
||||||
|
|
||||||
|
$builder->add('label', TextType::class, [
|
||||||
|
'label' => 'docgen.Label for third party',
|
||||||
|
'required' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
|
||||||
|
{
|
||||||
|
$this->personContext->buildPublicForm($builder, $template, $entity);
|
||||||
|
|
||||||
|
$builder->add('thirdParty', PickThirdpartyDynamicType::class, [
|
||||||
|
'multiple' => false,
|
||||||
|
'label' => $template->getOptions()['label'] ?? 'ThirdParty',
|
||||||
|
'validation_groups' => ['__none__'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
|
||||||
|
{
|
||||||
|
$data = $this->personContext->getData($template, $entity, $contextGenerationData);
|
||||||
|
|
||||||
|
$data['thirdParty'] = $this->normalizer->normalize(
|
||||||
|
$contextGenerationData['thirdParty'],
|
||||||
|
'docgen',
|
||||||
|
['docgen:expects' => ThirdParty::class, 'groups' => ['docgen:read']]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'docgen.A context for person with a third party (for sending mail)';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityClass(): string
|
||||||
|
{
|
||||||
|
return $this->personContext->getEntityClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormData(DocGeneratorTemplate $template, $entity): array
|
||||||
|
{
|
||||||
|
return $this->personContext->getFormData($template, $entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getKey(): string
|
||||||
|
{
|
||||||
|
return self::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'docgen.Person with third party';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAdminForm(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
|
||||||
|
{
|
||||||
|
$this->personContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData);
|
||||||
|
}
|
||||||
|
}
|
@ -193,7 +193,7 @@ final class SocialWorkMetadata implements SocialWorkMetadataInterface
|
|||||||
/** @var Evaluation $eval */
|
/** @var Evaluation $eval */
|
||||||
$eval = $this->getOrCreateEntity($this->evaluationRepository, 'title', ['fr' => $evaluationTitle]);
|
$eval = $this->getOrCreateEntity($this->evaluationRepository, 'title', ['fr' => $evaluationTitle]);
|
||||||
$eval->setTitle(['fr' => $evaluationTitle]);
|
$eval->setTitle(['fr' => $evaluationTitle]);
|
||||||
$eval->addSocialAction($socialAction);
|
$socialAction->addEvaluation($eval);
|
||||||
|
|
||||||
$this->entityManager->persist($eval);
|
$this->entityManager->persist($eval);
|
||||||
|
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
<?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\PersonBundle\Tests\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class AccompanyingPeriodOriginNormalizerTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->normalizer = self::$container->get(NormalizerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalization()
|
||||||
|
{
|
||||||
|
$o = new Origin();
|
||||||
|
|
||||||
|
$normalized = $this->normalizer->normalize(
|
||||||
|
$o,
|
||||||
|
'json',
|
||||||
|
['groups' => ['read']]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertIsArray($normalized);
|
||||||
|
$this->assertArrayHasKey('type', $normalized);
|
||||||
|
$this->assertEquals('origin', $normalized['type']);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
<?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 Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class AccompanyingPeriodResourceNormalizerTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
|
||||||
|
$this->normalizer = self::$container->get(NormalizerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeNullHasSameValueAsNotNull()
|
||||||
|
{
|
||||||
|
$nullResource = $this->normalizer->normalize(null, 'docgen', ['groups' => 'docgen:read', 'docgen:expects' => Resource::class]);
|
||||||
|
$notNull = $this->normalizer->normalize(new Resource(), 'docgen', ['groups' => 'docgen:read', 'docgen:expects' => Resource::class]);
|
||||||
|
|
||||||
|
$this->assertEqualsCanonicalizing(array_keys($notNull), array_keys($nullResource));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeResource()
|
||||||
|
{
|
||||||
|
$resource = new Resource();
|
||||||
|
$resource
|
||||||
|
->setComment('blabla')
|
||||||
|
->setResource(new ThirdParty());
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'type' => 'accompanying_period_resource',
|
||||||
|
'isNull' => false,
|
||||||
|
'comment' => 'blabla',
|
||||||
|
];
|
||||||
|
|
||||||
|
$actual = $this->normalizer->normalize($resource, 'docgen', ['groups' => 'docgen:read', 'docgen:expects' => Resource::class]);
|
||||||
|
|
||||||
|
// we do not test for sub array (person, thirdparty). We then check first for base value...
|
||||||
|
foreach ($expected as $key => $value) {
|
||||||
|
$this->assertArrayHasKey($key, $actual);
|
||||||
|
$this->assertEquals($value, $actual[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and then for the existence of some values
|
||||||
|
$this->assertArrayHasKey('person', $actual);
|
||||||
|
$this->assertArrayHasKey('thirdParty', $actual);
|
||||||
|
}
|
||||||
|
}
|
@ -60,7 +60,7 @@ final class AccompanyingPeriodWorkDocGenNormalizerTest extends KernelTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNormlalize()
|
public function testNormalize()
|
||||||
{
|
{
|
||||||
$work = new AccompanyingPeriodWork();
|
$work = new AccompanyingPeriodWork();
|
||||||
$work
|
$work
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
<?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\PersonBundle\Tests\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class SocialActionNormalizerTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->normalizer = self::$container->get(NormalizerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalization()
|
||||||
|
{
|
||||||
|
$sa = new SocialAction();
|
||||||
|
|
||||||
|
$normalized = $this->normalizer->normalize(
|
||||||
|
$sa,
|
||||||
|
'json',
|
||||||
|
['groups' => ['read']]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertIsArray($normalized);
|
||||||
|
$this->assertArrayHasKey('type', $normalized);
|
||||||
|
$this->assertEquals('social_work_social_action', $normalized['type']);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
<?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\PersonBundle\Tests\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class SocialIssueNormalizerTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->normalizer = self::$container->get(NormalizerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalization()
|
||||||
|
{
|
||||||
|
$si = new SocialIssue();
|
||||||
|
|
||||||
|
$normalized = $this->normalizer->normalize(
|
||||||
|
$si,
|
||||||
|
'json',
|
||||||
|
['groups' => ['read']]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertIsArray($normalized);
|
||||||
|
$this->assertArrayHasKey('type', $normalized);
|
||||||
|
$this->assertEquals('social_issue', $normalized['type']);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,286 @@
|
|||||||
|
<?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 Service\DocGenerator;
|
||||||
|
|
||||||
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
|
||||||
|
use Chill\DocStoreBundle\Entity\DocumentCategory;
|
||||||
|
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
|
||||||
|
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||||
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Form\Type\ScopePickerType;
|
||||||
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Service\DocGenerator\PersonContext;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\Exception\Prediction\FailedPredictionException;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use function count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class PersonContextTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the build person context works in the case when 'form_show_scope' is false.
|
||||||
|
*/
|
||||||
|
public function testScopeDoNotShowScopeInForms()
|
||||||
|
{
|
||||||
|
$person = new Person();
|
||||||
|
$docGen = (new DocGeneratorTemplate())
|
||||||
|
->setName(['fr' => 'template']);
|
||||||
|
|
||||||
|
$parameter = new ParameterBag(['chill_main' => ['acl' => ['form_show_scopes' => false]]]);
|
||||||
|
$em = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$em->persist(Argument::type(PersonDocument::class))
|
||||||
|
->should(static function ($calls, $object, $method) {
|
||||||
|
if (1 !== count($calls)) {
|
||||||
|
throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var PersonDocument $personDocument */
|
||||||
|
$personDocument = $calls[0]->getArguments()[0];
|
||||||
|
|
||||||
|
if (null !== $personDocument->getScope()) {
|
||||||
|
throw new FailedPredictionException('the person document should not have any scope');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$personContext = $this->buildPersonContext(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$em->reveal(),
|
||||||
|
null,
|
||||||
|
$parameter
|
||||||
|
);
|
||||||
|
|
||||||
|
$personContext->buildPublicForm($this->buildFormBuilder(false), $docGen, $person);
|
||||||
|
|
||||||
|
$personContext->storeGenerated(
|
||||||
|
$docGen,
|
||||||
|
new StoredObject(),
|
||||||
|
$person,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testScopeScopeMustBeShownInFormsAndUserAccessMultipleScope()
|
||||||
|
{
|
||||||
|
$person = new Person();
|
||||||
|
$docGen = (new DocGeneratorTemplate())
|
||||||
|
->setName(['fr' => 'template']);
|
||||||
|
$scope = new Scope();
|
||||||
|
|
||||||
|
$em = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$em->persist(Argument::type(PersonDocument::class))
|
||||||
|
->should(static function ($calls, $object, $method) use ($scope) {
|
||||||
|
if (1 !== count($calls)) {
|
||||||
|
throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var PersonDocument $personDocument */
|
||||||
|
$personDocument = $calls[0]->getArguments()[0];
|
||||||
|
|
||||||
|
if ($personDocument->getScope() !== $scope) {
|
||||||
|
throw new FailedPredictionException('the person document should show the exactly prepared scope');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
|
||||||
|
$authorizationHelper->getReachableScopes(Argument::type(UserInterface::class), PersonDocumentVoter::CREATE, Argument::type('array'))
|
||||||
|
->willReturn([$scope, new Scope()]);
|
||||||
|
|
||||||
|
$personContext = $this->buildPersonContext(
|
||||||
|
$authorizationHelper->reveal(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$em->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$personContext->buildPublicForm($this->buildFormBuilder(true), $docGen, $person);
|
||||||
|
|
||||||
|
$personContext->storeGenerated(
|
||||||
|
$docGen,
|
||||||
|
new StoredObject(),
|
||||||
|
$person,
|
||||||
|
['scope' => $scope]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testScopeScopeMustBeShownInFormsAndUserAccessOneScope()
|
||||||
|
{
|
||||||
|
$person = new Person();
|
||||||
|
$docGen = (new DocGeneratorTemplate())
|
||||||
|
->setName(['fr' => 'template']);
|
||||||
|
$scope = new Scope();
|
||||||
|
|
||||||
|
$em = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$em->persist(Argument::type(PersonDocument::class))
|
||||||
|
->should(static function ($calls, $object, $method) use ($scope) {
|
||||||
|
if (1 !== count($calls)) {
|
||||||
|
throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var PersonDocument $personDocument */
|
||||||
|
$personDocument = $calls[0]->getArguments()[0];
|
||||||
|
|
||||||
|
if ($personDocument->getScope() !== $scope) {
|
||||||
|
throw new FailedPredictionException('the person document should show the exactly prepared scope');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
|
||||||
|
$authorizationHelper->getReachableScopes(Argument::type(UserInterface::class), PersonDocumentVoter::CREATE, Argument::type('array'))
|
||||||
|
->willReturn([$scope]);
|
||||||
|
|
||||||
|
$personContext = $this->buildPersonContext(
|
||||||
|
$authorizationHelper->reveal(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$em->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$personContext->buildPublicForm($this->buildFormBuilder(true), $docGen, $person);
|
||||||
|
|
||||||
|
$personContext->storeGenerated(
|
||||||
|
$docGen,
|
||||||
|
new StoredObject(),
|
||||||
|
$person,
|
||||||
|
['scope' => $scope]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFormBuilder(bool $withScope): FormBuilderInterface
|
||||||
|
{
|
||||||
|
$builder = $this->prophesize(FormBuilderInterface::class);
|
||||||
|
|
||||||
|
$builder->add('title', TextType::class, Argument::type('array'))
|
||||||
|
->shouldBeCalled(1);
|
||||||
|
|
||||||
|
if ($withScope) {
|
||||||
|
$builder->add('scope', ScopePickerType::class, Argument::type('array'))
|
||||||
|
->shouldBeCalled();
|
||||||
|
} else {
|
||||||
|
$builder->add('scope', ScopePickerType::class, Argument::type('array'))
|
||||||
|
->shouldNotBeCalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $builder->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildPersonContext(
|
||||||
|
?AuthorizationHelperInterface $authorizationHelper = null,
|
||||||
|
?BaseContextData $baseContextData = null,
|
||||||
|
?CenterResolverManagerInterface $centerResolverManager = null,
|
||||||
|
?DocumentCategoryRepository $documentCategoryRepository = null,
|
||||||
|
?EntityManagerInterface $em = null,
|
||||||
|
?NormalizerInterface $normalizer = null,
|
||||||
|
?ParameterBagInterface $parameterBag = null,
|
||||||
|
?Security $security = null,
|
||||||
|
?TranslatorInterface $translator = null,
|
||||||
|
?TranslatableStringHelperInterface $translatableStringHelper = null
|
||||||
|
): PersonContext {
|
||||||
|
if (null === $authorizationHelper) {
|
||||||
|
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class)->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $baseContextData) {
|
||||||
|
$baseContextData = $this->prophesize(BaseContextData::class)->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $centerResolverManager) {
|
||||||
|
$centerResolverManager = $this->prophesize(CenterResolverManagerInterface::class);
|
||||||
|
$centerResolverManager->resolveCenters(Argument::any(), Argument::any())
|
||||||
|
->willReturn([new Center()]);
|
||||||
|
$centerResolverManager = $centerResolverManager->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $documentCategoryRepository) {
|
||||||
|
$documentCategoryRepository = $this->prophesize(DocumentCategoryRepository::class);
|
||||||
|
$documentCategoryRepository->find(Argument::type('integer'))->willReturn(
|
||||||
|
new DocumentCategory(PersonDocument::class, 1)
|
||||||
|
);
|
||||||
|
$documentCategoryRepository = $documentCategoryRepository->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $em) {
|
||||||
|
$em = $this->prophesize(EntityManagerInterface::class)->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $normalizer) {
|
||||||
|
$normalizer = $this->prophesize(NormalizerInterface::class);
|
||||||
|
$normalizer->normalize(Argument::type(Person::class), 'docgen', Argument::any())
|
||||||
|
->willReturn(['type' => 'person']);
|
||||||
|
$normalizer = $normalizer->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $parameterBag) {
|
||||||
|
$parameterBag = new ParameterBag(['chill_main' => ['acl' => ['form_show_scopes' => true]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $security) {
|
||||||
|
$security = $this->prophesize(Security::class);
|
||||||
|
$security->getUser()->willReturn(new User());
|
||||||
|
$security = $security->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $translator) {
|
||||||
|
$translator = $this->prophesize(TranslatorInterface::class)->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $translatableStringHelper) {
|
||||||
|
$translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
|
||||||
|
// return only the 'fr' key
|
||||||
|
$translatableStringHelper->localize(Argument::type('array'))->will(static function ($args) {
|
||||||
|
return $args[0]['fr'];
|
||||||
|
});
|
||||||
|
$translatableStringHelper = $translatableStringHelper->reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PersonContext(
|
||||||
|
$authorizationHelper,
|
||||||
|
$baseContextData,
|
||||||
|
$centerResolverManager,
|
||||||
|
$documentCategoryRepository,
|
||||||
|
$em,
|
||||||
|
$normalizer,
|
||||||
|
$parameterBag,
|
||||||
|
$security,
|
||||||
|
$translator,
|
||||||
|
$translatableStringHelper
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
<?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 Service\DocGenerator;
|
||||||
|
|
||||||
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Service\DocGenerator\PersonContextInterface;
|
||||||
|
use Chill\PersonBundle\Service\DocGenerator\PersonContextWithThirdParty;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class PersonContextWithThirdPartyTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
public function testAdminFormReverseTransform()
|
||||||
|
{
|
||||||
|
$personContext = $this->buildPersonContextWithThirdParty();
|
||||||
|
|
||||||
|
$actual = $personContext->adminFormReverseTransform(['label' => 'bloup']);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('category', $actual);
|
||||||
|
$this->assertArrayHasKey('label', $actual);
|
||||||
|
$this->assertEquals('bloup', $actual['label']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdminFormTransform()
|
||||||
|
{
|
||||||
|
$personContext = $this->buildPersonContextWithThirdParty();
|
||||||
|
|
||||||
|
$actual = $personContext->adminFormTransform(['label' => 'bloup']);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('from_person', $actual);
|
||||||
|
$this->assertArrayHasKey('label', $actual);
|
||||||
|
$this->assertEquals('bloup', $actual['label']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetData()
|
||||||
|
{
|
||||||
|
$personContext = $this->buildPersonContextWithThirdParty();
|
||||||
|
|
||||||
|
$actual = $personContext->getData(
|
||||||
|
(new DocGeneratorTemplate())->setOptions(['label' => 'bloup']),
|
||||||
|
new Person(),
|
||||||
|
['thirdParty' => $tp = new ThirdParty()]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('person', $actual);
|
||||||
|
$this->assertArrayHasKey('thirdParty', $actual);
|
||||||
|
$this->assertEquals(spl_object_hash($tp), $actual['thirdParty']['hash']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildPersonContextWithThirdParty(): PersonContextWithThirdParty
|
||||||
|
{
|
||||||
|
$normalizer = $this->prophesize(NormalizerInterface::class);
|
||||||
|
$normalizer->normalize(Argument::type(ThirdParty::class), 'docgen', Argument::type('array'))
|
||||||
|
->will(static function ($args): array {
|
||||||
|
return ['class' => '3party', 'hash' => spl_object_hash($args[0])];
|
||||||
|
});
|
||||||
|
|
||||||
|
$personContext = $this->prophesize(PersonContextInterface::class);
|
||||||
|
|
||||||
|
$personContext->adminFormReverseTransform(Argument::type('array'))->willReturn(
|
||||||
|
['category' => ['idInsideBundle' => 1, 'bundleId' => 'abc']]
|
||||||
|
);
|
||||||
|
$personContext->adminFormTransform(Argument::type('array'))->willReturn(
|
||||||
|
['from_person' => 'kept']
|
||||||
|
);
|
||||||
|
$personContext->getData(Argument::type(DocGeneratorTemplate::class), Argument::type(Person::class), Argument::type('array'))
|
||||||
|
->willReturn(['person' => 'data']);
|
||||||
|
|
||||||
|
return new PersonContextWithThirdParty(
|
||||||
|
$personContext->reveal(),
|
||||||
|
$normalizer->reveal()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -887,6 +887,10 @@ docgen:
|
|||||||
A context for accompanying period work evaluation: Contexte pour les évaluations dans les actions d'accompagnement
|
A context for accompanying period work evaluation: Contexte pour les évaluations dans les actions d'accompagnement
|
||||||
Person basic: Personne (basique)
|
Person basic: Personne (basique)
|
||||||
A basic context for person: Contexte pour les personnes
|
A basic context for person: Contexte pour les personnes
|
||||||
|
Person with third party: Personne avec choix d'un tiers
|
||||||
|
A context for person with a third party (for sending mail): Un contexte d'une personne avec un tiers (pour envoyer un courrier à ce tiers, par exemple)
|
||||||
|
Label for third party: Label à afficher aux utilisateurs
|
||||||
|
Document title: Titre du document généré
|
||||||
|
|
||||||
period_notification:
|
period_notification:
|
||||||
period_designated_subject: Vous êtes référent d'un parcours d'accompagnement
|
period_designated_subject: Vous êtes référent d'un parcours d'accompagnement
|
||||||
@ -937,6 +941,8 @@ reassign:
|
|||||||
All periods on this list will be reassigned to this user, excepted the one you manually reassigned before: Tous les parcours visibles sur cette page seront assignés à cet utilisateur, sauf ceux que vous aurez assigné à un utilisateur manuellement.
|
All periods on this list will be reassigned to this user, excepted the one you manually reassigned before: Tous les parcours visibles sur cette page seront assignés à cet utilisateur, sauf ceux que vous aurez assigné à un utilisateur manuellement.
|
||||||
Reassign: Assigner le référent
|
Reassign: Assigner le référent
|
||||||
List periods to be able to reassign them: Choisissez un utilisateur et cliquez sur "Filtrer" pour visualiser ses parcours. Vous pourrez ensuite les réassigner.
|
List periods to be able to reassign them: Choisissez un utilisateur et cliquez sur "Filtrer" pour visualiser ses parcours. Vous pourrez ensuite les réassigner.
|
||||||
|
Filter by postal code: Filtrer par code postal
|
||||||
|
Filter course which are located inside a postal code: Afficher uniquement les parcours localisés auprès de ce code postal (une commune peut comporter plusieurs codes postaux).
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
Notify referrer: Notifier le référent
|
Notify referrer: Notifier le référent
|
||||||
|
@ -214,7 +214,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(name="kind", type="string", length="20", options={"default": ""})
|
* @ORM\Column(name="kind", type="string", length="20", options={"default": ""})
|
||||||
* @Groups({"write"})
|
* @Groups({"write", "docgen:read", "docgen:read:3party:parent"})
|
||||||
*/
|
*/
|
||||||
private ?string $kind = '';
|
private ?string $kind = '';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user