Merge remote-tracking branch 'origin/master' into 110-export-editable

This commit is contained in:
Julien Fastré 2023-06-26 11:06:52 +02:00
commit 9f0fdb031a
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
60 changed files with 1286 additions and 248 deletions

View File

@ -0,0 +1,6 @@
kind: Fixed
body: '[Accompanying period comments]: order comments from the most recent to the
oldest, in the list'
time: 2023-06-21T13:28:51.282714011+02:00
custom:
Issue: ""

View File

@ -0,0 +1,5 @@
kind: Fixed
body: 'Api: filter social action to keep only the currently activated'
time: 2023-06-21T13:59:12.57760217+02:00
custom:
Issue: ""

View File

@ -0,0 +1,5 @@
kind: Fixed
body: Fix deletion and re-creation of filiation relationship
time: 2023-06-21T14:18:28.437876316+02:00
custom:
Issue: "82"

17
.changes/v2.1.0.md Normal file
View File

@ -0,0 +1,17 @@
## v2.1.0 - 2023-06-12
### Feature
* [docgen] allow to pick a third party when generating a document in context Activity, AccompanyingPeriod
### Fixed
* ([#111](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/111)) List of "my accompanying periods": separate the active and closed periods in two different lists, and show the inactive_long and inactive_short periods
### Security
* ([#105](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/105)) Rights are checked for display of 'accompanying period' tab in household menu. Rights are also checked for creation of 'accompanying period' from within household context
### DX
* Add methods to RegroupmentRepository and fullfill Center / Regroupment Doctrine mapping

12
.changes/v2.2.0.md Normal file
View File

@ -0,0 +1,12 @@
## v2.2.0 - 2023-06-18
### Feature
* When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document
* Add notification to accompanying period work and work's evaluation's documents
* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps
* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates
### Fixed
* use the correct annotation for the association between PersonCurrentCenter and Person
* ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox
* ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter
### DX
* DQL function OVERLAPSI: simplify expression in postgresql

3
.changes/v2.2.1.md Normal file
View File

@ -0,0 +1,3 @@
## v2.2.1 - 2023-06-19
### Fixed
* ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation

View File

@ -6,7 +6,7 @@ versionExt: md
versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}' versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
kindFormat: '### {{.Kind}}' kindFormat: '### {{.Kind}}'
changeFormat: >- changeFormat: >-
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{- end }}{{.Body}} * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}}
custom: custom:
- key: Issue - key: Issue
label: Issue number (on chill-bundles repository) (optional) label: Issue number (on chill-bundles repository) (optional)

View File

@ -6,6 +6,41 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v2.2.1 - 2023-06-19
### Fixed
* ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation
## v2.2.0 - 2023-06-18
### Feature
* When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document
* Add notification to accompanying period work and work's evaluation's documents
* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps
* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates
### Fixed
* use the correct annotation for the association between PersonCurrentCenter and Person
* ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox
* ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter
### DX
* DQL function OVERLAPSI: simplify expression in postgresql
## v2.1.0 - 2023-06-12
### Feature
* [docgen] allow to pick a third party when generating a document in context Activity, AccompanyingPeriod
### Fixed
* ([#111](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/111)) List of "my accompanying periods": separate the active and closed periods in two different lists, and show the inactive_long and inactive_short periods
### Security
* ([#105](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/105)) Rights are checked for display of 'accompanying period' tab in household menu. Rights are also checked for creation of 'accompanying period' from within household context
### DX
* Add methods to RegroupmentRepository and fullfill Center / Regroupment Doctrine mapping
## 2.0.0 ## 2.0.0
* this is a release to relaunch our proceess of release with semantic versioning * this is a release to relaunch our proceess of release with semantic versioning

View File

@ -44,3 +44,9 @@ async function download_and_open(event: Event): Promise<void> {
} }
</script> </script>
<style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

@ -88,3 +88,9 @@ async function download_and_open(event: Event): Promise<void> {
console.log('open button should have been clicked'); console.log('open button should have been clicked');
} }
</script> </script>
<style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

@ -40,5 +40,8 @@ async function beforeLeave(event: Event): Promise<true> {
</script> </script>
<style scoped lang="sass"> <style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style> </style>

View File

@ -1,5 +1,5 @@
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%} {%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
<div <div class="d-inline-flex"
data-download-buttons data-download-buttons
data-stored-object="{{ document_json|json_encode|escape('html_attr') }}" data-stored-object="{{ document_json|json_encode|escape('html_attr') }}"
data-can-edit="{{ can_edit ? '1' : '0' }}" data-can-edit="{{ can_edit ? '1' : '0' }}"

View File

@ -18,6 +18,7 @@ No document found: Aucun document trouvé
The document is successfully registered: Le document est enregistré The document is successfully registered: Le document est enregistré
The document is successfully updated: Le document est mis à jour The document is successfully updated: Le document est mis à jour
Any description: Aucune description Any description: Aucune description
See the document: Voir le document
document: document:
Any title: Aucun titre Any title: Aucun titre

View File

@ -90,16 +90,14 @@ class OverlapsI extends FunctionNode
if ($part instanceof PathExpression) { if ($part instanceof PathExpression) {
return sprintf( return sprintf(
"CASE WHEN %s IS NOT NULL THEN %s ELSE '%s'::date END", "COALESCE(%s, '%s'::date)",
$part->dispatch($sqlWalker),
$part->dispatch($sqlWalker), $part->dispatch($sqlWalker),
$p $p
); );
} }
return sprintf( return sprintf(
"CASE WHEN %s::date IS NOT NULL THEN %s::date ELSE '%s'::date END", "COALESCE(%s::date, '%s'::date)",
$part->dispatch($sqlWalker),
$part->dispatch($sqlWalker), $part->dispatch($sqlWalker),
$p $p
); );

View File

@ -48,12 +48,19 @@ class Center implements HasCenterInterface
*/ */
private string $name = ''; private string $name = '';
/**
* @var Collection<Regroupment>
* @ORM\ManyToMany(targetEntity=Regroupment::class, mappedBy="centers")
*/
private Collection $regroupments;
/** /**
* Center constructor. * Center constructor.
*/ */
public function __construct() public function __construct()
{ {
$this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection(); $this->groupCenters = new ArrayCollection();
$this->regroupments = new ArrayCollection();
} }
/** /**
@ -106,6 +113,14 @@ class Center implements HasCenterInterface
return $this->name; return $this->name;
} }
/**
* @return Collection<Regroupment>
*/
public function getRegroupments(): Collection
{
return $this->regroupments;
}
/** /**
* @param $name * @param $name
* *

View File

@ -22,11 +22,12 @@ use Doctrine\ORM\Mapping as ORM;
class Regroupment class Regroupment
{ {
/** /**
* @var Center
* @ORM\ManyToMany( * @ORM\ManyToMany(
* targetEntity=Center::class * targetEntity=Center::class,
* inversedBy="regroupments"
* ) * )
* @ORM\Id * @ORM\Id
* @var Collection<Center>
*/ */
private Collection $centers; private Collection $centers;
@ -52,6 +53,26 @@ class Regroupment
$this->centers = new ArrayCollection(); $this->centers = new ArrayCollection();
} }
public function addCenter(Center $center): self
{
if (!$this->centers->contains($center)) {
$this->centers->add($center);
$center->getRegroupments()->add($this);
}
return $this;
}
public function removeCenter(Center $center): self
{
if ($this->centers->contains($center)) {
$this->centers->removeElement($center);
$center->getRegroupments()->removeElement($this);
}
return $this;
}
public function getCenters(): Collection public function getCenters(): Collection
{ {
return $this->centers; return $this->centers;

View File

@ -31,7 +31,7 @@ class RegroupmentType extends AbstractType
->add('centers', EntityType::class, [ ->add('centers', EntityType::class, [
'class' => Center::class, 'class' => Center::class,
'multiple' => true, 'multiple' => true,
'attr' => ['class' => 'select2'], 'expanded' => true,
]) ])
->add('isActive', CheckboxType::class, [ ->add('isActive', CheckboxType::class, [
'label' => 'Actif ?', 'label' => 'Actif ?',

View File

@ -34,9 +34,13 @@ class NotificationPresence
$this->notificationRepository = $notificationRepository; $this->notificationRepository = $notificationRepository;
} }
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array{unread: int, sent: int, total: int}
*/
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): array
{ {
if (array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) { if ([] === $more && array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) {
return $this->cache[$relatedEntityClass][$relatedEntityId]; return $this->cache[$relatedEntityClass][$relatedEntityId];
} }
@ -46,21 +50,25 @@ class NotificationPresence
$counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated( $counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated(
$relatedEntityClass, $relatedEntityClass,
$relatedEntityId, $relatedEntityId,
$user $user,
$more
); );
if ([] === $more) {
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter; $this->cache[$relatedEntityClass][$relatedEntityId] = $counter;
}
return $counter; return $counter;
} }
return ['unread' => 0, 'read' => 0]; return ['unread' => 0, 'sent' => 0, 'total' => 0];
} }
/** /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array|Notification[] * @return array|Notification[]
*/ */
public function getNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array public function getNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId, array $more = []): array
{ {
$user = $this->security->getUser(); $user = $this->security->getUser();
@ -68,7 +76,8 @@ class NotificationPresence
return $this->notificationRepository->findNotificationByRelatedEntityAndUserAssociated( return $this->notificationRepository->findNotificationByRelatedEntityAndUserAssociated(
$relatedEntityClass, $relatedEntityClass,
$relatedEntityId, $relatedEntityId,
$user $user,
$more
); );
} }

View File

@ -34,24 +34,30 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
$this->urlGenerator = $urlGenerator; $this->urlGenerator = $urlGenerator;
} }
public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): string
{ {
return $environment->render( return $environment->render(
'@ChillMain/Notification/extension_counter_notifications_for.html.twig', '@ChillMain/Notification/extension_counter_notifications_for.html.twig',
[ [
'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId), 'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more),
] ]
); );
} }
public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $options = []): array /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
*/
public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): array
{ {
return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId); return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more);
} }
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
*/
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): string
{ {
$notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId); $notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more);
if ([] === $notifications) { if ([] === $notifications) {
return ''; return '';

View File

@ -29,6 +29,15 @@ final class NotificationRepository implements ObjectRepository
private EntityRepository $repository; private EntityRepository $repository;
private const BASE_COUNTER_SQL = <<<'SQL'
SELECT
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = :userid and cmnau.notification_id = cmn.id))::int) AS unread,
SUM((cmn.sender_id = :userid)::int) AS sent,
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_user cmnau_all WHERE user_id = :userid and cmnau_all.notification_id = cmn.id))::int) + SUM((cmn.sender_id = :userid)::int) AS total
FROM chill_main_notification cmn
SQL;
public function __construct(EntityManagerInterface $entityManager) public function __construct(EntityManagerInterface $entityManager)
{ {
$this->em = $entityManager; $this->em = $entityManager;
@ -51,29 +60,45 @@ final class NotificationRepository implements ObjectRepository
->getSingleScalarResult(); ->getSingleScalarResult();
} }
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array{unread: int, sent: int, total: int}
*/
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more = []): array
{ {
$sqlParams = ['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()];
if ([] === $more) {
if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) { if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) {
$sql = $sql = self::BASE_COUNTER_SQL . ' WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL';
'SELECT
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = :userid and cmnau.notification_id = cmn.id))::int) AS unread,
SUM((cmn.sender_id = :userid)::int) AS sent,
COUNT(cmn.*) AS total
FROM chill_main_notification cmn
WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL';
$this->notificationByRelatedEntityAndUserAssociatedStatement = $this->notificationByRelatedEntityAndUserAssociatedStatement =
$this->em->getConnection()->prepare($sql); $this->em->getConnection()->prepare($sql);
} }
$results = $this->notificationByRelatedEntityAndUserAssociatedStatement $results = $this->notificationByRelatedEntityAndUserAssociatedStatement
->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()]); ->executeQuery($sqlParams);
$result = $results->fetchAssociative(); $result = $results->fetchAssociative();
$results->free(); $results->free();
} else {
$wheres = [];
foreach ([
['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId],
...$more
] as $k => ['relatedEntityClass' => $relClass, 'relatedEntityId' => $relId]) {
$wheres[] = "(relatedEntityClass = :relatedEntityClass_{$k} AND relatedEntityId = :relatedEntityId_{$k})";
$sqlParams["relatedEntityClass_{$k}"] = $relClass;
$sqlParams["relatedEntityId_{$k}"] = $relId;
}
return $result; $sql = self::BASE_COUNTER_SQL . ' WHERE sender_id IS NOT NULL AND (' . implode(' OR ', $wheres) . ')';
$result = $this->em->getConnection()->fetchAssociative($sql, $sqlParams);
}
return array_map(fn (?int $number) => $number ?? 0, $result);
} }
public function countUnreadByUser(User $user): int public function countUnreadByUser(User $user): int
@ -167,8 +192,8 @@ final class NotificationRepository implements ObjectRepository
} }
/** /**
* @param mixed|null $limit * @param int|null $limit
* @param mixed|null $offset * @param int|null $offset
* *
* @return Notification[] * @return Notification[]
*/ */
@ -178,13 +203,15 @@ final class NotificationRepository implements ObjectRepository
} }
/** /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array|Notification[] * @return array|Notification[]
*/ */
public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more): array
{ {
return return
$this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user) $this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user, $more)
->select('n') ->select('n')
->addOrderBy('n.date', 'DESC')
->getQuery() ->getQuery()
->getResult(); ->getResult();
} }
@ -222,13 +249,36 @@ final class NotificationRepository implements ObjectRepository
return Notification::class; return Notification::class;
} }
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): QueryBuilder /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
*/
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more = []): QueryBuilder
{ {
$qb = $this->repository->createQueryBuilder('n'); $qb = $this->repository->createQueryBuilder('n');
// add condition for related entity (in main arguments, and in more)
$or = $qb->expr()->orX($qb->expr()->andX(
$qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'),
$qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')
));
$qb $qb
->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass')) ->setParameter('relatedEntityClass', $relatedEntityClass)
->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')) ->setParameter('relatedEntityId', $relatedEntityId);
foreach ($more as $k => ['relatedEntityClass' => $relatedClass, 'relatedEntityId' => $relatedId]) {
$or->add(
$qb->expr()->andX(
$qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass_'.$k),
$qb->expr()->eq('n.relatedEntityId', ':relatedEntityId_'.$k)
)
);
$qb
->setParameter('relatedEntityClass_'.$k, $relatedClass)
->setParameter('relatedEntityId_'.$k, $relatedId);
}
$qb
->andWhere($or)
->andWhere($qb->expr()->isNotNull('n.sender')) ->andWhere($qb->expr()->isNotNull('n.sender'))
->andWhere( ->andWhere(
$qb->expr()->orX( $qb->expr()->orX(
@ -236,8 +286,6 @@ final class NotificationRepository implements ObjectRepository
$qb->expr()->eq('n.sender', ':user') $qb->expr()->eq('n.sender', ':user')
) )
) )
->setParameter('relatedEntityClass', $relatedEntityClass)
->setParameter('relatedEntityId', $relatedEntityId)
->setParameter('user', $user); ->setParameter('user', $user);
return $qb; return $qb;

View File

@ -14,6 +14,8 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Regroupment; use Chill\MainBundle\Entity\Regroupment;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\Persistence\ObjectRepository; use Doctrine\Persistence\ObjectRepository;
final class RegroupmentRepository implements ObjectRepository final class RegroupmentRepository implements ObjectRepository
@ -59,6 +61,30 @@ final class RegroupmentRepository implements ObjectRepository
return $this->repository->findOneBy($criteria, $orderBy); return $this->repository->findOneBy($criteria, $orderBy);
} }
/**
* @throws NonUniqueResultException
* @throws NoResultException
*/
public function findOneByName(string $name): ?Regroupment
{
return $this->repository->createQueryBuilder('r')
->where('LOWER(r.name) = LOWER(:searched)')
->setParameter('searched', $name)
->getQuery()
->getSingleResult();
}
/**
* @return array<Regroupment>
*/
public function findRegroupmentAssociatedToNoCenter(): array
{
return $this->repository->createQueryBuilder('r')
->where('SIZE(r.centers) = 0')
->getQuery()
->getResult();
}
public function getClassName() public function getClassName()
{ {
return Regroupment::class; return Regroupment::class;

View File

@ -0,0 +1,20 @@
const buildLinkCreate = function (relatedEntityClass: string, relatedEntityId: number, to: number | null, returnPath: string | null): string
{
const params = new URLSearchParams();
params.append('entityClass', relatedEntityClass);
params.append('entityId', relatedEntityId.toString());
if (null !== to) {
params.append('tos[0]', to.toString());
}
if (null !== returnPath) {
params.append('returnPath', returnPath);
}
return `/fr/notification/create?${params.toString()}`;
}
export {
buildLinkCreate,
}

View File

@ -16,21 +16,19 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use function count; use function count;
class SocialWorkSocialActionApiController extends ApiController final class SocialWorkSocialActionApiController extends ApiController
{ {
private PaginatorFactory $paginator; public function __construct(
private readonly SocialIssueRepository $socialIssueRepository,
private SocialIssueRepository $socialIssueRepository; private readonly PaginatorFactory $paginator,
private readonly ClockInterface $clock,
public function __construct(SocialIssueRepository $socialIssueRepository, PaginatorFactory $paginator) ) {
{
$this->socialIssueRepository = $socialIssueRepository;
$this->paginator = $paginator;
} }
public function listBySocialIssueApi($id, Request $request) public function listBySocialIssueApi($id, Request $request)
@ -42,7 +40,10 @@ class SocialWorkSocialActionApiController extends ApiController
throw $this->createNotFoundException('socialIssue not found'); throw $this->createNotFoundException('socialIssue not found');
} }
$socialActions = $socialIssue->getRecursiveSocialActions()->toArray(); $socialActions = SocialAction::filterRemoveDeactivatedActions(
$socialIssue->getRecursiveSocialActions()->toArray(),
\DateTime::createFromImmutable($this->clock->now())
);
usort($socialActions, static fn (SocialAction $sa, SocialAction $sb) => $sa->getOrdering() <=> $sb->getOrdering()); usort($socialActions, static fn (SocialAction $sa, SocialAction $sb) => $sa->getOrdering() <=> $sb->getOrdering());

View File

@ -12,9 +12,11 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller; namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
class UserAccompanyingPeriodController extends AbstractController class UserAccompanyingPeriodController extends AbstractController
@ -32,12 +34,24 @@ class UserAccompanyingPeriodController extends AbstractController
/** /**
* @Route("/{_locale}/person/accompanying-periods/my", name="chill_person_accompanying_period_user") * @Route("/{_locale}/person/accompanying-periods/my", name="chill_person_accompanying_period_user")
*/ */
public function listAction(Request $request) public function listAction(Request $request): Response
{ {
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']]); $active = $request->query->getBoolean('active', true);
$steps = match ($active) {
true => [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
],
false => [
AccompanyingPeriod::STEP_CLOSED,
]
};
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => $steps]);
$pagination = $this->paginatorFactory->create($total); $pagination = $this->paginatorFactory->create($total);
$accompanyingPeriods = $this->accompanyingPeriodRepository->findBy( $accompanyingPeriods = $this->accompanyingPeriodRepository->findBy(
['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']], ['user' => $this->getUser(), 'step' => $steps],
['openingDate' => 'DESC'], ['openingDate' => 'DESC'],
$pagination->getItemsPerPage(), $pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber() $pagination->getCurrentPageFirstItemNumber()
@ -46,13 +60,14 @@ class UserAccompanyingPeriodController extends AbstractController
return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [ return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [
'accompanyingPeriods' => $accompanyingPeriods, 'accompanyingPeriods' => $accompanyingPeriods,
'pagination' => $pagination, 'pagination' => $pagination,
'active' => $active,
]); ]);
} }
/** /**
* @Route("/{_locale}/person/accompanying-periods/my/drafts", name="chill_person_accompanying_period_draft_user") * @Route("/{_locale}/person/accompanying-periods/my/drafts", name="chill_person_accompanying_period_draft_user")
*/ */
public function listDraftsAction(Request $request) public function listDraftsAction(): Response
{ {
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => 'DRAFT']); $total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => 'DRAFT']);
$pagination = $this->paginatorFactory->create($total); $pagination = $this->paginatorFactory->create($total);

View File

@ -185,7 +185,9 @@ class AccompanyingPeriod implements
* cascade={"persist", "remove"}, * cascade={"persist", "remove"},
* orphanRemoval=true * orphanRemoval=true
* ) * )
* @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT}) * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT})
* @var Collection<Comment>
*/ */
private Collection $comments; private Collection $comments;
@ -705,10 +707,11 @@ class AccompanyingPeriod implements
->comments ->comments
->filter( ->filter(
static fn (Comment $c): bool => $c !== $pinnedComment static fn (Comment $c): bool => $c !== $pinnedComment
); )
;
} }
public function getCreatedAt(): ?DateTime public function getCreatedAt(): DateTimeInterface
{ {
return $this->createdAt; return $this->createdAt;
} }

View File

@ -59,6 +59,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
* ) * )
* @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"read", "docgen:read"})
* @ORM\OrderBy({"startDate": "DESC", "id": "DESC"}) * @ORM\OrderBy({"startDate": "DESC", "id": "DESC"})
* @var Collection<AccompanyingPeriodWorkEvaluation>
* *
* @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer` * @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer`
*/ */
@ -278,6 +279,9 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this->accompanyingPeriod; return $this->accompanyingPeriod;
} }
/**
* @return Collection<AccompanyingPeriodWorkEvaluation>
*/
public function getAccompanyingPeriodWorkEvaluations(): Collection public function getAccompanyingPeriodWorkEvaluations(): Collection
{ {
return $this->accompanyingPeriodWorkEvaluations; return $this->accompanyingPeriodWorkEvaluations;

View File

@ -79,6 +79,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* ) * )
* @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"}) * @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"})
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
* @var Collection<AccompanyingPeriodWorkEvaluationDocument>
*/ */
private Collection $documents; private Collection $documents;
@ -204,7 +205,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
} }
/** /**
* @return Collection * @return Collection<AccompanyingPeriodWorkEvaluationDocument>
*/ */
public function getDocuments() public function getDocuments()
{ {

View File

@ -45,7 +45,7 @@ class PersonCenterCurrent
private ?int $id = null; private ?int $id = null;
/** /**
* @ORM\ManyToOne(targetEntity=Person::class, inversedBy="centerCurrent") * @ORM\OneToOne(targetEntity=Person::class, inversedBy="centerCurrent")
*/ */
private Person $person; private Person $person;

View File

@ -15,6 +15,7 @@ use DateInterval;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Serializer\Annotation as Serializer;
@ -295,6 +296,19 @@ class SocialAction
return 0 < $this->getChildren()->count(); return 0 < $this->getChildren()->count();
} }
public function isDesactivated(\DateTime $atDate): bool
{
if (null !== $this->desactivationDate && $this->desactivationDate < $atDate) {
return true;
}
if ($this->hasParent()) {
return $this->parent->isDesactivated($atDate);
}
return false;
}
public function hasParent(): bool public function hasParent(): bool
{ {
return $this->getParent() instanceof self; return $this->getParent() instanceof self;
@ -401,4 +415,14 @@ class SocialAction
return $this; return $this;
} }
public static function filterRemoveDeactivatedActions(ReadableCollection|array $actions, \DateTime $comparisonDate): ReadableCollection|array
{
$filterFn = fn (SocialAction $socialAction) => !$socialAction->isDesactivated($comparisonDate);
return match ($actions instanceof ReadableCollection) {
true => $actions->filter($filterFn),
false => array_filter($actions, $filterFn)
};
}
} }

View File

@ -253,7 +253,7 @@ class SocialIssue
} }
/** /**
* @return Collection|SocialAction[] All the descendant social actions of all * @return Collection<SocialAction> All the descendant social actions of all
* the descendants of the entity * the descendants of the entity
*/ */
public function getRecursiveSocialActions(): Collection public function getRecursiveSocialActions(): Collection
@ -272,7 +272,7 @@ class SocialIssue
} }
/** /**
* @return Collection|SocialAction[] * @return Collection<SocialAction>
*/ */
public function getSocialActions(): Collection public function getSocialActions(): Collection
{ {

View File

@ -0,0 +1,125 @@
<?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\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
class StepFilterBetweenDates implements FilterInterface
{
private const DEFAULT_CHOICE = [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
];
private const STEPS = [
'course.draft' => AccompanyingPeriod::STEP_DRAFT,
'course.confirmed' => AccompanyingPeriod::STEP_CONFIRMED,
'course.closed' => AccompanyingPeriod::STEP_CLOSED,
'course.inactive_short' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
'course.inactive_long' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
];
private RollingDateConverterInterface $rollingDateConverter;
private TranslatorInterface $translator;
public function __construct(
RollingDateConverterInterface $rollingDateConverter,
TranslatorInterface $translator
) {
$this->rollingDateConverter = $rollingDateConverter;
$this->translator = $translator;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$alias = 'acp_filter_by_step_between_dat_alias';
$steps = 'acp_filter_by_step_between_dat_steps';
$from = 'acp_filter_by_step_between_dat_from';
$to = 'acp_filter_by_step_between_dat_to';
$qb
->andWhere(
$qb->expr()->exists(
"SELECT 1 FROM " . AccompanyingPeriod\AccompanyingPeriodStepHistory::class . " {$alias} " .
"WHERE {$alias}.step IN (:{$steps}) AND OVERLAPSI ({$alias}.startDate, {$alias}.endDate),(:{$from}, :{$to}) = TRUE " .
"AND {$alias}.period = acp"
)
)
->setParameter($from, $this->rollingDateConverter->convert($data['date_from']))
->setParameter($to, $this->rollingDateConverter->convert($data['date_to']))
->setParameter($steps, $data['accepted_steps_multi']);
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('accepted_steps_multi', ChoiceType::class, [
'label' => 'export.filter.course.by_step.steps',
'choices' => self::STEPS,
'multiple' => true,
'expanded' => true,
])
->add('date_from', PickRollingDateType::class, [
'label' => 'export.filter.course.by_step.date_from',
])
->add('date_to', PickRollingDateType::class, [
'label' => 'export.filter.course.by_step.date_to',
]);
}
public function getFormDefaultData(): array
{
return [
'accepted_steps_multi' => self::DEFAULT_CHOICE,
'date_from' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
'date_to' => new RollingDate(RollingDate::T_TODAY),
];
}
public function describeAction($data, $format = 'string')
{
$steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']);
return ['export.filter.course.by_step.Filtered by steps: only %step% and between %date_from% and %date_to%', [
'%step%' => implode(', ', $steps),
'%date_from%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'),
'%date_to%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'),
]];
}
public function getTitle()
{
return 'export.filter.course.by_step.Filter by step between dates';
}
}

View File

@ -23,11 +23,15 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array; use function in_array;
class StepFilter implements FilterInterface class StepFilterOnDate implements FilterInterface
{ {
private const A = 'acp_filter_bystep_stephistories'; private const A = 'acp_filter_bystep_stephistories';
private const DEFAULT_CHOICE = AccompanyingPeriod::STEP_CONFIRMED; private const DEFAULT_CHOICE = [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
];
private const P = 'acp_step_filter_date'; private const P = 'acp_step_filter_date';
@ -79,7 +83,7 @@ class StepFilter implements FilterInterface
$qb->expr()->in(self::A . '.step', ':acp_filter_by_step_steps') $qb->expr()->in(self::A . '.step', ':acp_filter_by_step_steps')
) )
->setParameter(self::P, $this->rollingDateConverter->convert($data['calc_date'])) ->setParameter(self::P, $this->rollingDateConverter->convert($data['calc_date']))
->setParameter('acp_filter_by_step_steps', $data['accepted_steps']); ->setParameter('acp_filter_by_step_steps', $data['accepted_steps_multi']);
} }
public function applyOn() public function applyOn()
@ -90,32 +94,36 @@ class StepFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder $builder
->add('accepted_steps', ChoiceType::class, [ ->add('accepted_steps_multi', ChoiceType::class, [
'label' => 'export.filter.course.by_step.steps',
'choices' => self::STEPS, 'choices' => self::STEPS,
'multiple' => false, 'multiple' => true,
'expanded' => true, 'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
]) ])
->add('calc_date', PickRollingDateType::class, [ ->add('calc_date', PickRollingDateType::class, [
'label' => 'export.filter.course.by_step.date_calc', 'label' => 'export.filter.course.by_step.date_calc',
]); ]);
} }
public function getFormDefaultData(): array public function getFormDefaultData(): array
{ {
return ['accepted_steps' => self::DEFAULT_CHOICE, 'calc_date' => new RollingDate(RollingDate::T_TODAY)]; return [
'accepted_steps_multi' => self::DEFAULT_CHOICE,
'calc_date' => new RollingDate(RollingDate::T_TODAY),
];
} }
public function describeAction($data, $format = 'string') public function describeAction($data, $format = 'string')
{ {
$step = array_flip(self::STEPS)[$data['accepted_steps']]; $steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']);
return ['Filtered by steps: only %step%', [ return ['Filtered by steps: only %step%', [
'%step%' => $this->translator->trans($step), '%step%' => implode(', ', $steps),
]]; ]];
} }
public function getTitle() public function getTitle()
{ {
return 'Filter by step'; return 'export.filter.course.by_step.Filter by step';
} }
} }

View File

@ -12,7 +12,9 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Menu; namespace Chill\PersonBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Knp\Menu\MenuItem; use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class HouseholdMenuBuilder implements LocalMenuBuilderInterface class HouseholdMenuBuilder implements LocalMenuBuilderInterface
@ -22,9 +24,12 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
*/ */
protected $translator; protected $translator;
public function __construct(TranslatorInterface $translator) private Security $security;
public function __construct(TranslatorInterface $translator, Security $security)
{ {
$this->translator = $translator; $this->translator = $translator;
$this->security = $security;
} }
public function buildMenu($menuId, MenuItem $menu, array $parameters): void public function buildMenu($menuId, MenuItem $menu, array $parameters): void
@ -53,12 +58,14 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
], ]) ], ])
->setExtras(['order' => 17]); ->setExtras(['order' => 17]);
if ($this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['household'])) {
$menu->addChild($this->translator->trans('household.Accompanying period'), [ $menu->addChild($this->translator->trans('household.Accompanying period'), [
'route' => 'chill_person_household_accompanying_period', 'route' => 'chill_person_household_accompanying_period',
'routeParameters' => [ 'routeParameters' => [
'household_id' => $household->getId(), 'household_id' => $household->getId(),
],]) ],])
->setExtras(['order' => 20]); ->setExtras(['order' => 20]);
}
$menu->addChild($this->translator->trans('household.Addresses'), [ $menu->addChild($this->translator->trans('household.Addresses'), [
'route' => 'chill_person_household_addresses', 'route' => 'chill_person_household_addresses',

View File

@ -27,7 +27,7 @@ final class AccompanyingPeriodNotificationHandler implements NotificationHandler
public function getTemplate(Notification $notification, array $options = []): string public function getTemplate(Notification $notification, array $options = []): string
{ {
return 'ChillPersonBundle:AccompanyingPeriod:showInNotification.html.twig'; return '@ChillPerson/AccompanyingPeriod/showInNotification.html.twig';
} }
public function getTemplateData(Notification $notification, array $options = []): array public function getTemplateData(Notification $notification, array $options = []): array

View File

@ -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\PersonBundle\Notification;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository;
final class AccompanyingPeriodWorkEvaluationDocumentNotificationHandler implements NotificationHandlerInterface
{
private AccompanyingPeriodWorkEvaluationDocumentRepository $accompanyingPeriodWorkEvaluationDocumentRepository;
public function __construct(AccompanyingPeriodWorkEvaluationDocumentRepository $accompanyingPeriodWorkEvaluationDocumentRepository)
{
$this->accompanyingPeriodWorkEvaluationDocumentRepository = $accompanyingPeriodWorkEvaluationDocumentRepository;
}
public function getTemplate(Notification $notification, array $options = []): string
{
return '@ChillPerson/AccompanyingCourseWork/showEvaluationDocumentInNotification.html.twig';
}
public function getTemplateData(Notification $notification, array $options = []): array
{
return [
'notification' => $notification,
'document' => $doc = $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($notification->getRelatedEntityId()),
'evaluation' => $doc?->getAccompanyingPeriodWorkEvaluation(),
];
}
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluationDocument::class;
}
}

View File

@ -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\Notification;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
final class AccompanyingPeriodWorkNotificationHandler implements NotificationHandlerInterface
{
private AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository;
public function __construct(AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository)
{
$this->accompanyingPeriodWorkRepository = $accompanyingPeriodWorkRepository;
}
public function getTemplate(Notification $notification, array $options = []): string
{
return '@ChillPerson/AccompanyingCourseWork/showInNotification.html.twig';
}
public function getTemplateData(Notification $notification, array $options = []): array
{
return [
'notification' => $notification,
'work' => $this->accompanyingPeriodWorkRepository->find($notification->getRelatedEntityId()),
];
}
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() === AccompanyingPeriodWork::class;
}
}

View File

@ -122,7 +122,8 @@
<add-evaluation <add-evaluation
v-for="e in pickedEvaluations" v-for="e in pickedEvaluations"
v-bind:key="e.key" v-bind:key="e.key"
v-bind:evaluation="e"> v-bind:evaluation="e"
v-bind:docAnchorId="this.docAnchorId">
</add-evaluation> </add-evaluation>
<!-- box to add new evaluation --> <!-- box to add new evaluation -->
@ -297,6 +298,20 @@
></list-workflow-modal> ></list-workflow-modal>
</li> </li>
<li>
<button v-if="AmIRefferer"
class="btn btn-notify"
@click="goToGenerateNotification(false)"
></button>
<template v-else>
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" :title="$t('notification_send')" data-bs-toggle="dropdown" aria-expanded="false">&nbsp;</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li><a class="dropdown-item" @click="goToGenerateNotification(true)">{{ $t('notification_notify_referrer') }}</a></li>
<li><a class="dropdown-item" @click="goToGenerateNotification(false)">{{ $t('notification_notify_any') }}</a></li>
</ul>
</template>
</li>
<li v-if="!isPosting"> <li v-if="!isPosting">
<button class="btn btn-save" @click="submit"> <button class="btn btn-save" @click="submit">
{{ $t('action.save') }} {{ $t('action.save') }}
@ -366,7 +381,10 @@ const i18n = {
no_referrers: "Aucun agent traitant", no_referrers: "Aucun agent traitant",
choose_referrers: "Choisir des agents traitants", choose_referrers: "Choisir des agents traitants",
remove_referrer: "Enlever l'agent", remove_referrer: "Enlever l'agent",
private_comment: "Commentaire privé" private_comment: "Commentaire privé",
notification_notify_referrer: "Notifier le référent",
notification_notify_any: "Notifier d'autres utilisateurs",
notification_send: "Envoyer une notification",
} }
} }
}; };
@ -389,6 +407,7 @@ export default {
i18n, i18n,
data() { data() {
return { return {
docAnchorId: null,
isExpanded: false, isExpanded: false,
editor: ClassicEditor, editor: ClassicEditor,
showAddObjective: false, showAddObjective: false,
@ -428,6 +447,13 @@ export default {
}, },
}; };
}, },
beforeMount() {
const urlParams = new URLSearchParams(window.location.search);
this.docAnchorId = urlParams.get('doc_id');
},
mounted() {
this.scrollToElement(this.docAnchorId);
},
computed: { computed: {
...mapState([ ...mapState([
'work', 'work',
@ -441,6 +467,7 @@ export default {
'isPosting', 'isPosting',
'errors', 'errors',
'templatesAvailablesForAction', 'templatesAvailablesForAction',
'me',
]), ]),
...mapGetters([ ...mapGetters([
'hasResultsForAction', 'hasResultsForAction',
@ -498,6 +525,10 @@ export default {
this.$store.commit('setPersonsPickedIds', v); this.$store.commit('setPersonsPickedIds', v);
} }
}, },
AmIRefferer() {
return (!(this.work.accompanyingPeriod.user && this.me
&& (this.work.accompanyingPeriod.user.id !== this.me.id)));
}
}, },
methods: { methods: {
toggleSelect() { toggleSelect() {
@ -548,6 +579,19 @@ export default {
return this.$store.dispatch('submit', callback) return this.$store.dispatch('submit', callback)
.catch(e => { console.log(e); throw e; }); .catch(e => { console.log(e); throw e; });
}, },
goToGenerateNotification(tos) {
console.log('save before leave to notification');
const callback = (data) => {
if (tos === true) {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork&entityId=${this.work.id}&tos[0]=${this.work.accompanyingPeriod.user.id}&returnPath=/fr/person/accompanying-period/${this.work.accompanyingPeriod.id}/work`);
} else {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork&entityId=${this.work.id}&returnPath=/fr/person/accompanying-period/${this.work.accompanyingPeriod.id}/work`);
}
}
return this.$store.dispatch('submit', callback)
.catch(e => {console.log(e); throw e});
},
submit() { submit() {
this.$store.dispatch('submit').catch((error) => { this.$store.dispatch('submit').catch((error) => {
if (error.name === 'ValidationException' || error.name === 'AccessException') { if (error.name === 'ValidationException' || error.name === 'AccessException') {
@ -559,7 +603,7 @@ export default {
}); });
}, },
saveFormOnTheFly(payload) { saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data); // console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
let body = { type: payload.type }; let body = { type: payload.type };
body.name = payload.data.text; body.name = payload.data.text;
@ -581,6 +625,12 @@ export default {
this.$toast.open({message: 'An error occurred'}); this.$toast.open({message: 'An error occurred'});
} }
}) })
},
scrollToElement(docAnchorId) {
const documentEl = document.getElementById(`document_${docAnchorId}`);
if (documentEl) {
documentEl.scrollIntoView({behavior: 'smooth'});
}
} }
} }
}; };

View File

@ -11,7 +11,7 @@
</div> </div>
<div> <div>
<form-evaluation ref="FormEvaluation" :key="evaluation.key" :evaluation="evaluation"></form-evaluation> <form-evaluation ref="FormEvaluation" :key="evaluation.key" :evaluation="evaluation" :docAnchorId="docAnchorId"></form-evaluation>
<ul class="record_actions"> <ul class="record_actions">
<li v-if="evaluation.workflows_availables.length > 0"> <li v-if="evaluation.workflows_availables.length > 0">
@ -85,7 +85,7 @@ export default {
Modal, Modal,
ListWorkflowModal, ListWorkflowModal,
}, },
props: ['evaluation'], props: ['evaluation', 'docAnchorId'],
i18n, i18n,
data() { data() {
return { return {

View File

@ -79,8 +79,8 @@
<h5>{{ $t('Documents') }} :</h5> <h5>{{ $t('Documents') }} :</h5>
<div class="flex-table"> <div class="flex-table">
<div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="d.id"> <div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="d.id" :class="[parseInt(this.docAnchorId) === d.id ? 'bg-blink' : 'nothing']">
<div class="item-row"> <div :id="`document_${d.id}`" class="item-row">
<div class="input-group input-group-lg mb-3 row"> <div class="input-group input-group-lg mb-3 row">
<label class="col-sm-3 col-form-label">Titre du document:</label> <label class="col-sm-3 col-form-label">Titre du document:</label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -116,13 +116,18 @@
></list-workflow-modal> ></list-workflow-modal>
</li> </li>
<li> <li>
<add-async-upload <button
:buttonTitle="$t('replace')" v-if="AmIRefferer"
:options="asyncUploadOptions" class="btn btn-notify"
:btnClasses="{'btn': true, 'btn-edit': true}" @click="goToGenerateDocumentNotification(d, false)">
@addDocument="(arg) => replaceDocument(d, arg)" </button>
> <template v-else>
</add-async-upload> <button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" :title="$t('notification_send')" data-bs-toggle="dropdown" aria-expanded="false">&nbsp;</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li><a class="dropdown-item" @click="goToGenerateDocumentNotification(d, true)">{{ $t('notification_notify_referrer') }}</a></li>
<li><a class="dropdown-item" @click="goToGenerateDocumentNotification(d, false)">{{ $t('notification_notify_any') }}</a></li>
</ul>
</template>
</li> </li>
<li> <li>
<document-action-buttons-group <document-action-buttons-group
@ -133,6 +138,15 @@
@on-stored-object-status-change="onStatusDocumentChanged" @on-stored-object-status-change="onStatusDocumentChanged"
></document-action-buttons-group> ></document-action-buttons-group>
</li> </li>
<li>
<add-async-upload
:buttonTitle="$t('replace')"
:options="asyncUploadOptions"
:btnClasses="{'btn': true, 'btn-edit': true}"
@addDocument="(arg) => replaceDocument(d, arg)"
>
</add-async-upload>
</li>
<li v-if="d.workflows.length === 0"> <li v-if="d.workflows.length === 0">
<a class="btn btn-delete" @click="removeDocument(d)"> <a class="btn btn-delete" @click="removeDocument(d)">
</a> </a>
@ -187,6 +201,7 @@ import AddAsyncUpload from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUpload
import AddAsyncUploadDownloader from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUploadDownloader.vue'; import AddAsyncUploadDownloader from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUploadDownloader.vue';
import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue'; import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js'; import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
import {buildLinkCreate as buildLinkCreateNotification} from 'ChillMainAssets/lib/entity-notification/api';
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
const i18n = { const i18n = {
@ -214,14 +229,17 @@ const i18n = {
template_title: "Nom du template", template_title: "Nom du template",
browse: "Ajouter un document", browse: "Ajouter un document",
replace: "Remplacer", replace: "Remplacer",
download: "Télécharger le fichier existant" download: "Télécharger le fichier existant",
notification_notify_referrer: "Notifier le référent",
notification_notify_any: "Notifier d'autres utilisateurs",
notification_send: "Envoyer une notification",
} }
} }
}; };
export default { export default {
name: "FormEvaluation", name: "FormEvaluation",
props: ['evaluation'], props: ['evaluation', 'docAnchorId'],
components: { components: {
ckeditor: CKEditor.component, ckeditor: CKEditor.component,
PickTemplate, PickTemplate,
@ -260,8 +278,14 @@ export default {
}, },
computed: { computed: {
...mapState([ ...mapState([
'isPosting' 'isPosting',
'work',
'me',
]), ]),
AmIRefferer() {
return (!(this.$store.state.work.accompanyingPeriod.user && this.$store.state.me
&& (this.$store.state.work.accompanyingPeriod.user.id !== this.$store.state.me.id)));
},
getTemplatesAvailables() { getTemplatesAvailables() {
return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation); return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation);
}, },
@ -390,6 +414,20 @@ export default {
return this.$store.dispatch('submit', callback) return this.$store.dispatch('submit', callback)
.catch(e => { console.log(e); throw e; }); .catch(e => { console.log(e); throw e; });
}, },
goToGenerateDocumentNotification(document, tos) {
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key);
let updatedDocument = evaluation.documents.find(d => d.key === document.key);
window.location.assign(buildLinkCreateNotification(
'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
updatedDocument.id,
tos === true ? this.$store.state.work.accompanyingPeriod.user.id : null,
window.location.pathname + window.location.search + window.location.hash
));
};
return this.$store.dispatch('submit', callback)
.catch(e => {console.log(e); throw e});
}
}, },
} }
</script> </script>
@ -402,4 +440,19 @@ export default {
ul.document-upload { ul.document-upload {
justify-content: flex-start; justify-content: flex-start;
} }
.bg-blink{
color: #050000;
padding: 10px;
display: inline-block;
border-radius: 5px;
animation: blinkingBackground 2.2s infinite;
animation-iteration-count: 2;
}
@keyframes blinkingBackground{
0% { background-color: #ed776d;}
50% { background-color: #ffffff;}
100% { background-color: #ed776d;}
}
</style> </style>

View File

@ -35,6 +35,7 @@ const store = createStore({
referrers: window.accompanyingCourseWork.referrers, referrers: window.accompanyingCourseWork.referrers,
isPosting: false, isPosting: false,
errors: [], errors: [],
me: null
}, },
getters: { getters: {
socialAction(state) { socialAction(state) {
@ -130,6 +131,9 @@ const store = createStore({
} }
}, },
mutations: { mutations: {
setWhoAmiI(state, me) {
state.me = me;
},
setEvaluationsPicked(state, evaluations) { setEvaluationsPicked(state, evaluations) {
state.evaluationsPicked = evaluations.map((e, index) => { state.evaluationsPicked = evaluations.map((e, index) => {
var k = Object.assign(e, { var k = Object.assign(e, {
@ -385,6 +389,19 @@ const store = createStore({
}, },
}, },
actions: { actions: {
getWhoAmI({ commit }) {
let url = `/api/1.0/main/whoami.json`;
window.fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw { m: 'Error while retriving results for goal', s: response.status, b: response.body };
})
.then(data => {
commit('setWhoAmiI', data);
});
},
updateThirdParty({ commit }, payload) { updateThirdParty({ commit }, payload) {
commit('updateThirdParty', payload); commit('updateThirdParty', payload);
}, },
@ -514,6 +531,7 @@ store.commit('setEvaluationsPicked', window.accompanyingCourseWork.accompanyingP
store.dispatch('getReachablesResultsForAction'); store.dispatch('getReachablesResultsForAction');
store.dispatch('getReachablesGoalsForAction'); store.dispatch('getReachablesGoalsForAction');
store.dispatch('getReachablesEvaluationsForAction'); store.dispatch('getReachablesEvaluationsForAction');
store.dispatch('getWhoAmI');
store.state.evaluationsPicked.forEach(evaluation => { store.state.evaluationsPicked.forEach(evaluation => {
store.dispatch('fetchTemplatesAvailablesForEvaluation', evaluation.evaluation) store.dispatch('fetchTemplatesAvailablesForEvaluation', evaluation.evaluation)

View File

@ -207,7 +207,7 @@
</template> </template>
<script> <script>
import {dateToISO} from 'ChillMainAssets/chill/js/date'; import {dateToISO, ISOToDate} from 'ChillMainAssets/chill/js/date';
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue'; import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue'; import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue';
import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue'; import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue';
@ -262,7 +262,7 @@ export default {
}, },
birthdate: function () { birthdate: function () {
if (this.person.birthdate !== null || this.person.birthdate === "undefined") { if (this.person.birthdate !== null || this.person.birthdate === "undefined") {
return new Date(this.person.birthdate.datetime); return ISOToDate(this.person.birthdate.datetime);
} else { } else {
return ""; return "";
} }

View File

@ -5,7 +5,8 @@
# - displayAction: [true|false] default: false # - displayAction: [true|false] default: false
# - displayFontSmall: [true|false] default: false # - displayFontSmall: [true|false] default: false
#} #}
<div class="item-bloc{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}"> <div
class="item-bloc{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<div class="item-row"> <div class="item-row">
<h2 class="badge-title"> <h2 class="badge-title">
@ -111,9 +112,11 @@
</div> </div>
{% if displayContent is not defined or displayContent == 'short' %} {% if displayContent is not defined or displayContent == 'short' %}
<div class="item-row column{% if displayFontSmall is defined and displayFontSmall == true %} smallfont{% endif %}"> <div
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with { class="item-row column{% if displayFontSmall is defined and displayFontSmall == true %} smallfont{% endif %}">
'displayContent': displayContent {% include '@ChillPerson/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent,
'onlyone': false
} %} } %}
</div> </div>
{% endif %} {% endif %}
@ -130,6 +133,27 @@
{% if displayAction is defined and displayAction == true %} {% if displayAction is defined and displayAction == true %}
<ul class="item-col record_actions"> <ul class="item-col record_actions">
<li>{{ macro.workflowButton(w) }}</li> <li>{{ macro.workflowButton(w) }}</li>
{% if displayNotification is defined and displayNotification == true %}
<li>
<div class="d-grid gap-2 {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}btn-group{% endif %}" {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}role="group"{% endif %}>
{% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}" data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify" title="{{ 'notification.Notify'|trans }}" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id}) }}">
</a>
{% endif %}
</div>
</li>
{% endif %}
<li> <li>
<a class="btn btn-show" title="{{ 'Show'|trans }}" <a class="btn btn-show" title="{{ 'Show'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': w.id }) }}" href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': w.id }) }}"
@ -169,9 +193,11 @@
<h2 class="chill-blue">{{ 'Dispositifs' }}</h2> <h2 class="chill-blue">{{ 'Dispositifs' }}</h2>
<div class="flex-table">{# new flex-table wrapper #} <div class="flex-table">{# new flex-table wrapper #}
<div class="item-bloc colored{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}"> <div
class="item-bloc colored{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with { {% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent 'displayContent': displayContent,
'onlyone' : false
} %} } %}
</div> </div>
</div> </div>
@ -180,7 +206,8 @@
<h2 class="chill-blue">{{ 'Comments'|trans }}</h2> <h2 class="chill-blue">{{ 'Comments'|trans }}</h2>
<div class="flex-table"> <div class="flex-table">
<div class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}"> <div
class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Public</h3> <h3 class="chill-beige">Public</h3>
{% if w.note is not empty %} {% if w.note is not empty %}
<blockquote class="chill-user-quote"> <blockquote class="chill-user-quote">
@ -191,7 +218,8 @@
{% endif %} {% endif %}
</div> </div>
{% if w.privateComment.hasCommentForUser(app.user) %} {% if w.privateComment.hasCommentForUser(app.user) %}
<div class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}"> <div
class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Privé</h3> <h3 class="chill-beige">Privé</h3>
<blockquote class="chill-user-quote private-quote"> <blockquote class="chill-user-quote private-quote">
{{ w.privateComment.commentForUser(app.user)|chill_markdown_to_html }} {{ w.privateComment.commentForUser(app.user)|chill_markdown_to_html }}

View File

@ -1,7 +1,17 @@
{% macro metadata(w) %} {% macro metadata(w, include_notif_counter = true) %}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) %} {% if include_notif_counter == true %}
{% set more = [] %}
{% for e in w.accompanyingPeriodWorkEvaluations %}
{% for d in e.documents %}
{% set more = more|merge([{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id}]) %}
{% endfor %}
{% endfor %}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, more) %}
{% if notif_counter.total > 0 %} {% if notif_counter.total > 0 %}
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) }} {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, more) }}
{% endif %}
{% endif %} {% endif %}
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %} {% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
{{ macro.updatedBy(w) }} {{ macro.updatedBy(w) }}

View File

@ -3,6 +3,7 @@
# - displayContent: [short|long] default: short # - displayContent: [short|long] default: short
#} #}
{% if w.results|length > 0 %} {% if w.results|length > 0 %}
<table class="obj-res-eval"> <table class="obj-res-eval">
<thead> <thead>
<th class="obj"><h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4></th> <th class="obj"><h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4></th>
@ -64,7 +65,9 @@
</th> </th>
</thead> </thead>
<tbody> <tbody>
{% if onlyone|default(false) %}
{% for e in w.accompanyingPeriodWorkEvaluations %} {% for e in w.accompanyingPeriodWorkEvaluations %}
{% if evalId is defined and evalId == e.id %}
<tr> <tr>
<td class="eval"> <td class="eval">
<ul class="eval_title"> <ul class="eval_title">
@ -78,13 +81,15 @@
{% if e.endDate %} {% if e.endDate %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ e.endDate|format_date('short') }}</b> <b>{{ e.endDate|format_date('short') }}</b>
</li> </li>
{% else %} {% else %}
{% if displayContent is defined and displayContent == 'long' %} {% if displayContent is defined and displayContent == 'long' %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
@ -92,13 +97,15 @@
{% if e.maxDate %} {% if e.maxDate %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ e.maxDate|format_date('short') }}</b> <b>{{ e.maxDate|format_date('short') }}</b>
</li> </li>
{% else %} {% else %}
{% if displayContent is defined and displayContent == 'long' %} {% if displayContent is defined and displayContent == 'long' %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
@ -107,13 +114,15 @@
{% if e.warningInterval and e.warningInterval.d > 0 %} {% if e.warningInterval and e.warningInterval.d > 0 %}
<li> <li>
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %} {% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }} {{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li> </li>
{% else %} {% else %}
{% if displayContent is defined and displayContent == 'long' %} {% if displayContent is defined and displayContent == 'long' %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
@ -122,33 +131,153 @@
{% if e.timeSpent is not null and e.timeSpent > 0 %} {% if e.timeSpent is not null and e.timeSpent > 0 %}
<li> <li>
{% set minutes = (e.timeSpent / 60) %} {% set minutes = (e.timeSpent / 60) %}
<span class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }} <span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }}
</li> </li>
{% elseif displayContent is defined and displayContent == 'long' %} {% elseif displayContent is defined and displayContent == 'long' %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</li> </li>
</ul> </ul>
{% endif %}
{% endfor %}
{% if recordAction is defined %}
{{ recordAction }}
{% endif %}
{% else %}
{% for e in w.accompanyingPeriodWorkEvaluations %}
<tr>
<td class="eval">
<ul class="eval_title">
<li>
{{ e.evaluation.title|localize_translatable_string }}
<ul class="columns">
<li>
<span
class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ e.startDate|format_date('short') }}</b>
</li>
{% if e.endDate %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ e.endDate|format_date('short') }}</b>
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.maxDate %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ e.maxDate|format_date('short') }}</b>
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.warningInterval and e.warningInterval.d > 0 %}
<li>
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
<span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.timeSpent is not null and e.timeSpent > 0 %}
<li>
{% set minutes = (e.timeSpent / 60) %}
<span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }}
</li>
{% elseif displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
</ul>
</li>
</ul>
{% if recordAction is defined %}
{{ recordAction }}
{% endif %}
{% if displayContent is defined and displayContent == 'long' %} {% if displayContent is defined and displayContent == 'long' %}
{% if e.comment is not empty %} {% if e.comment is not empty %}
<blockquote class="chill-user-quote">{{ e.comment|chill_entity_render_box }}</blockquote> <blockquote
class="chill-user-quote">{{ e.comment|chill_entity_render_box }}</blockquote>
{% endif %} {% endif %}
{% import "@ChillDocStore/Macro/macro.html.twig" as m %} {% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} {% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% if e.documents|length > 0 %} {% if e.documents|length > 0 %}
<table class="table mt-4 mx-auto"> <table class="table table-hover align-middle mt-4 mx-auto">
{% for d in e.documents %} {% for d in e.documents %}
<tr class="border-0"> <tr>
<td class="border-0">{{ d.title }}</td> <td class="border-0">{{ d.title }}</td>
<td class="border-0">{{ mm.mimeIcon(d.storedObject.type) }}</td> <td class="border-0">{{ mm.mimeIcon(d.storedObject.type) }}</td>
<td class="border-0 text-end">{{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}</td> <td class="border-0 text-end">
{% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
<button id="btnGroupNotifyButtons" type="button" class="btn btn-sm btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}"
data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify btn-sm"
title="{{ 'notification.Notify'|trans }}"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id }) }}"></a>
{% endif %}
{{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
@ -161,6 +290,7 @@
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endif %}
</tbody> </tbody>
</table> </table>
{% endif %} {% endif %}

View File

@ -26,7 +26,8 @@
'displayAction': true, 'displayAction': true,
'displayContent': 'short', 'displayContent': 'short',
'displayFontSmall': true, 'displayFontSmall': true,
'itemBlocClass': '' 'itemBlocClass': '',
'displayNotification': true
} %} } %}
{% endfor %} {% endfor %}
</div> </div>

View File

@ -27,7 +27,20 @@
'displayContent': 'long', 'displayContent': 'long',
'itemBlocClass': 'uniq extended', 'itemBlocClass': 'uniq extended',
} %} } %}
<div class="p-3 mt-3">{{ macro.metadata(work) }}</div> <div class="p-3 mt-3">{{ macro.metadata(work, false) }}</div>
</div>
<div class="notification notification-list">
{% set more = [] %}
{% for e in work.accompanyingPeriodWorkEvaluations %}
{% for d in e.documents %}
{% set more = more|merge([{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id}]) %}
{% endfor %}
{% endfor %}
{% set notifications = chill_list_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', work.id, more) %}
{% if notifications is not empty %}
{{ notifications|raw }}
{% endif %}
</div> </div>
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
@ -36,6 +49,25 @@
class="btn btn-cancel">{{ 'Back to the list'|trans }}</a> class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
</li> </li>
<li>{{ macro.workflowButton(work) }}</li> <li>{{ macro.workflowButton(work) }}</li>
<li>
<div class="d-grid gap-2 {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}btn-group{% endif %}" {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}role="group"{% endif %}>
{% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}" data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify" title="{{ 'notification.Notify'|trans }}" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id}) }}">
</a>
{% endif %}
</div>
</li>
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', work) %} {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', work) %}
<li> <li>
<a class="btn btn-edit" <a class="btn btn-edit"
@ -47,7 +79,8 @@
<li> <li>
<a class="btn btn-delete" <a class="btn btn-delete"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': work.id } ) }}" href="{{ path('chill_person_accompanying_period_work_delete', { 'id': work.id } ) }}"
>{{ 'Delete'|trans }}</a> title = "{{ 'Delete'|trans }}"
></a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View File

@ -0,0 +1,134 @@
{% if document is not null %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
<div class="flex-table accompanying-course-work">
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_DOCUMENT_SHOW', document) %}
{% set doc = document %}
<div class="item-bloc evaluation-item bg-chill-llight-gray">
<div class="item-row mb-2">
<h1>{{ "Document"|trans }}: {{ doc.title }}</h1>
</div>
<div class="item-row mb-2">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ evaluation.accompanyingPeriodWork.socialAction|chill_entity_render_string }}
<ul class="small_in_title columns mt-1">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.startDate|format_date('short') }}</b>
</li>
{% if evaluation.accompanyingPeriodWork.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.endDate|format_date('short') }}</b>
</li>
{% endif %}
</ul>
</span>
</h2>
</div>
<div class="item-row mb-2">
<div class="item-col" style="width: 17%;">
<h4 class="title_label">
{{ 'Participants'|trans }}
</h4>
</div>
<div class="item-col list">
{% for p in evaluation.accompanyingPeriodWork.persons %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: p.id },
action: 'show',
displayBadge: true,
buttonText: p|chill_entity_render_string,
isDead: p.deathdate is not null
} %}
{% endfor %}
</div>
</div>
<div class="item-row column">
<table class="obj-res-eval my-3">
<thead>
<tr>
<th class="eval">
<h4 class="title_label">
{{ 'Évaluation'|trans }}
</h4>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="eval">
<ul class="eval_title">
<li class="my-2">
{{ evaluation.evaluation.title|localize_translatable_string }}
<ul class="columns pt-2">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.startDate|format_date('short') }}</b>
</li>
{% if evaluation.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.endDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.maxDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.maxDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.warningInterval and evaluation.warningInterval.d > 0 %}
<li>
{% set days = (evaluation.warningInterval.d + evaluation.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% endif %}
<li>
{% if evaluation.createdBy is not null %}
<span class="item-key">créé par</span>
<b>{{ evaluation.createdBy.username }}</b>
{% endif %}
{% if evaluation.createdAt is not null %}
<span class="item-key">{{ 'le'|trans }}</span>
<b>{{ evaluation.createdAt|format_date('short') }}</b>
{% endif %}
</li>
</ul>
{% if evaluation.comment %}
<blockquote class="chill-user-quote" style="margin-left: 0;">
{{ evaluation.comment }}
</blockquote>
{% endif %}
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
<div class="item-row">
<ul class="record_actions">
<li>
<a class="btn btn-show"
href="{{ path('chill_person_accompanying_period_work_show', { 'id': document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork.id }) }}"
title="{{ 'See the document'|trans }}"></a>
</li>
</ul>
</div>
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'This is the minimal period details'|trans ~ ': ' ~ document.id }}<br>
{{ 'You are getting a notification for a period you are not allowed to see'|trans }}
</div>
{% endif %}
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'You are getting a notification for a period which does not exists any more'|trans }}
</div>
{% endif %}

View File

@ -0,0 +1,30 @@
{% macro recordAction(work) %}
<li>
<a class="btn btn-show" title="{{ 'Show'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': work.id }) }}"
></a>
</li>
{% endmacro %}
{% if work is not null %}
<div class="flex-table accompanying-course-work">
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE', work) %}
{% include "@ChillPerson/AccompanyingCourseWork/_item.html.twig" with {
'itemBlocClass': 'bg-chill-llight-gray',
'displayAction': true,
'displayContent': 'short',
'displayFontSmall': true,
'displayNotification:':true,
'w': work
} %}
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'This is the minimal period details'|trans ~ ': ' ~ work.id }}<br>
{{ 'You are getting a notification for a period you are not allowed to see'|trans }}
</div>
{% endif %}
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'You are getting a notification for a period which does not exists any more'|trans }}
</div>
{% endif %}

View File

@ -8,7 +8,7 @@
{% if period is not null %} {% if period is not null %}
<div class="flex-table"> <div class="flex-table">
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', period) %} {% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', period) %}
{% include 'ChillPersonBundle:AccompanyingPeriod:_list_item.html.twig' with { {% include "@ChillPerson/AccompanyingPeriod/_list_item.html.twig" with {
'recordAction': _self.recordAction(notification.relatedEntityId), 'recordAction': _self.recordAction(notification.relatedEntityId),
'itemBlocClass': 'bg-chill-llight-gray' 'itemBlocClass': 'bg-chill-llight-gray'
} %} } %}

View File

@ -17,6 +17,15 @@
<div class="col-md-10"> <div class="col-md-10">
<h1>{{ 'My accompanying periods'|trans }}</h1> <h1>{{ 'My accompanying periods'|trans }}</h1>
<ul class="nav nav-pills justify-content-center">
<li class="nav-item">
<a class="nav-link {% if active == true %}active{% endif %}" aria-current="page" href="{{ chill_path_forward_return_path('chill_person_accompanying_period_user', {'active': true}) }}">{{ ['Confirmed'|trans, 'course.inactive_short'|trans, 'course.inactive_long'|trans]|join(', ') }}</a>
</li>
<li class="nav-item ">
<a class="nav-link {% if active == false %}active{% endif %}" href="{{ chill_path_forward_return_path('chill_person_accompanying_period_user', {'active': false}) }}">{{ 'course.closed'|trans }}</a>
</li>
</ul>
<p>{{ 'Number of periods'|trans }}: <span class="badge rounded-pill bg-primary">{{ pagination.totalItems }}</span></p> <p>{{ 'Number of periods'|trans }}: <span class="badge rounded-pill bg-primary">{{ pagination.totalItems }}</span></p>
<div class="flex-table accompanyingcourse-list"> <div class="flex-table accompanyingcourse-list">

View File

@ -40,13 +40,14 @@
{{ 'Household summary'|trans }} {{ 'Household summary'|trans }}
</a> </a>
</li> </li>
{# TODO: add ACL to check if user is allowed to edit household? #} {% if is_granted('CHILL_PERSON_HOUSEHOLD_EDIT', household) %}
<li> <li>
<a class="btn btn-create" <a class="btn btn-create"
href="{{ path ('chill_household_accompanying_course_new', {'household_id' : household.id } ) }}" role="button"> href="{{ path ('chill_household_accompanying_course_new', {'household_id' : household.id } ) }}" role="button">
{{ 'Create an accompanying period'|trans }} {{ 'Create an accompanying period'|trans }}
</a> </a>
</li> </li>
{% endif %}
</ul> </ul>
</div> </div>

View File

@ -20,7 +20,7 @@
<td>{{ entity.ordering }}</td> <td>{{ entity.ordering }}</td>
<td> <td>
{% if entity.desactivationDate is not null %} {% if entity.desactivationDate is not null %}
{{ entity.desactivationDate|date('Y-m-d') }} {{ entity.desactivationDate|format_date('medium') }}
{% endif %} {% endif %}
</td> </td>
<td> <td>

View File

@ -123,7 +123,7 @@
<ul class="record_actions"> <ul class="record_actions">
<li>{{ doc.storedObject|chill_document_button_group(doc.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork)) }}</li> <li>{{ doc.storedObject|chill_document_button_group(doc.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork)) }}</li>
<li> <li>
<a class="btn btn-show" href="{{ path('chill_person_accompanying_period_work_edit', {'id': evaluation.accompanyingPeriodWork.id}) }}"> <a class="btn btn-show" href="{{ path('chill_person_accompanying_period_work_edit', {'id': evaluation.accompanyingPeriodWork.id, 'doc_id': doc.id}) }}">
{{ 'Show'|trans }} {{ 'Show'|trans }}
</a> </a>
</li> </li>

View File

@ -13,7 +13,7 @@ namespace Chill\PersonBundle\Tests\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilter; use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilterOnDate;
/** /**
* @internal * @internal
@ -21,7 +21,7 @@ use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilter;
*/ */
final class StepFilterTest extends AbstractFilterTest final class StepFilterTest extends AbstractFilterTest
{ {
private StepFilter $filter; private StepFilterOnDate $filter;
protected function setUp(): void protected function setUp(): void
{ {

View File

@ -46,11 +46,13 @@ class RelationshipNoDuplicateValidator extends ConstraintValidator
]); ]);
foreach ($relationships as $r) { foreach ($relationships as $r) {
if ( if (spl_object_hash($r) !== spl_object_hash($value)
$r->getFromPerson() === $fromPerson and
|| $r->getFromPerson() === $toPerson (
|| $r->getToPerson() === $fromPerson ($r->getFromPerson() === $fromPerson and $r->getToPerson() === $toPerson)
|| $r->getToPerson() === $toPerson ||
($r->getFromPerson() === $toPerson and $r->getToPerson() === $fromPerson)
)
) { ) {
$this->context->buildViolation($constraint->message) $this->context->buildViolation($constraint->message)
->addViolation(); ->addViolation();

View File

@ -28,11 +28,14 @@ services:
tags: tags:
- { name: chill.export_filter, alias: accompanyingcourse_socialissue_filter } - { name: chill.export_filter, alias: accompanyingcourse_socialissue_filter }
chill.person.export.filter_step: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilterOnDate:
class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilter
tags: tags:
- { name: chill.export_filter, alias: accompanyingcourse_step_filter } - { name: chill.export_filter, alias: accompanyingcourse_step_filter }
Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilterBetweenDates:
tags:
- { name: chill.export_filter, alias: accompanyingcourse_step_filter_between_dates }
chill.person.export.filter_geographicalunitstat: chill.person.export.filter_geographicalunitstat:
class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\GeographicalUnitStatFilter class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\GeographicalUnitStatFilter
tags: tags:

View File

@ -2,3 +2,9 @@ services:
Chill\PersonBundle\Notification\AccompanyingPeriodNotificationHandler: Chill\PersonBundle\Notification\AccompanyingPeriodNotificationHandler:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
Chill\PersonBundle\Notification\AccompanyingPeriodWorkNotificationHandler:
autowire: true
autoconfigure: true
Chill\PersonBundle\Notification\AccompanyingPeriodWorkEvaluationDocumentNotificationHandler:
autowire: true
autoconfigure: true

View File

@ -473,7 +473,6 @@ Accepted socialissues: Problématiques sociales
"Filtered by socialissues: only %socialissues%": "Filtré par problématique sociale: uniquement %socialissues%" "Filtered by socialissues: only %socialissues%": "Filtré par problématique sociale: uniquement %socialissues%"
Group by social issue: Grouper les parcours par problématiques sociales Group by social issue: Grouper les parcours par problématiques sociales
Filter by step: Filtrer les parcours par statut du parcours
Accepted steps: Statuts Accepted steps: Statuts
Step: Statut Step: Statut
"Filtered by steps: only %step%": "Filtré par statut du parcours: uniquement %step%" "Filtered by steps: only %step%": "Filtré par statut du parcours: uniquement %step%"
@ -1085,7 +1084,13 @@ export:
title: Filter les parcours par intervenant title: Filter les parcours par intervenant
'Filtered by user working on course: only %users%': 'Filtré par intervenants sur le parcours: seulement %users%' 'Filtered by user working on course: only %users%': 'Filtré par intervenants sur le parcours: seulement %users%'
by_step: by_step:
Filter by step: Filtrer les parcours par statut du parcours
Filter by step between dates: Filtrer les parcours par statut du parcours entre deux dates
steps: Statuts retenus
date_calc: Date de prise en compte du statut date_calc: Date de prise en compte du statut
date_from: Statuts acquis après cette date
date_to: Statuts acquis avant cette date
'Filtered by steps: only %step% and between %date_from% and %date_to%': 'Filtré par statut: seulement %step%, entre %date_from% et %date_to%'
by_user_scope: by_user_scope:
Computation date for referrer: Date à laquelle le référent était actif Computation date for referrer: Date à laquelle le référent était actif
by_referrer: by_referrer:

View File

@ -166,7 +166,7 @@ class LoadReports extends AbstractFixture implements ContainerAwareInterface, Or
/** /**
* pick a random choice. * pick a random choice.
* *
* @return string|string[] the array of slug if multiple, a single slug otherwise * @return string|string[]
*/ */
private function getRandomChoice(CustomField $field) private function getRandomChoice(CustomField $field)
{ {
@ -211,6 +211,8 @@ class LoadReports extends AbstractFixture implements ContainerAwareInterface, Or
return $result; return $result;
} }
} }
return $picked;
} }
/** /**
@ -226,7 +228,7 @@ class LoadReports extends AbstractFixture implements ContainerAwareInterface, Or
/** /**
* pick a choice within a 'choices' options (for choice type). * pick a choice within a 'choices' options (for choice type).
* *
* @return the slug of the selected choice * @return string the slug of the selected choice
*/ */
private function pickChoice(array $choices) private function pickChoice(array $choices)
{ {