Merge remote-tracking branch 'origin/master' into issue388_order_social_issues

This commit is contained in:
2022-02-11 10:54:37 +01:00
74 changed files with 1209 additions and 453 deletions

View File

@@ -15,6 +15,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
@@ -23,7 +24,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingCourseWorkController extends AbstractController
{
@@ -60,7 +61,7 @@ class AccompanyingCourseWorkController extends AbstractController
*/
public function createWork(AccompanyingPeriod $period): Response
{
// TODO ACL
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::CREATE, $period);
if ($period->getSocialIssues()->count() === 0) {
$this->addFlash(
@@ -93,7 +94,8 @@ class AccompanyingCourseWorkController extends AbstractController
*/
public function deleteWork(AccompanyingPeriodWork $work, Request $request): Response
{
// TODO ACL
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::UPDATE, $work);
$em = $this->getDoctrine()->getManager();
$form = $this->createDeleteForm($work->getId());
@@ -138,7 +140,8 @@ class AccompanyingCourseWorkController extends AbstractController
*/
public function editWork(AccompanyingPeriodWork $work): Response
{
// TODO ACL
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::UPDATE, $work);
$json = $this->serializer->normalize($work, 'json', ['groups' => ['read']]);
return $this->render('@ChillPerson/AccompanyingCourseWork/edit.html.twig', [
@@ -157,13 +160,13 @@ class AccompanyingCourseWorkController extends AbstractController
*/
public function listWorkByAccompanyingPeriod(AccompanyingPeriod $period): Response
{
// TODO ACL
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::SEE, $period);
$totalItems = $this->workRepository->countByAccompanyingPeriod($period);
$paginator = $this->paginator->create($totalItems);
$works = $this->workRepository->findByAccompanyingPeriod(
$works = $this->workRepository->findByAccompanyingPeriodOpenFirst(
$period,
['startDate' => 'DESC', 'endDate' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);

View File

@@ -99,7 +99,7 @@ class AccompanyingPeriodWorkEvaluationApiController
if ($request->query->getBoolean('countOnly', false)) {
return new JsonResponse(
$this->serializer->serialize(new Counter($total), 'json', ['groups' => 'read']),
$this->serializer->serialize(new Counter($total), 'json', ['groups' => ['read']]),
JsonResponse::HTTP_OK,
[],
true
@@ -117,7 +117,7 @@ class AccompanyingPeriodWorkEvaluationApiController
$collection = new Collection($works, $paginator);
return new JsonResponse(
$this->serializer->serialize($collection, 'json', ['groups' => 'read']),
$this->serializer->serialize($collection, 'json', ['groups' => ['read', 'read:evaluation:include-work']]),
JsonResponse::HTTP_OK,
[],
true

View File

@@ -16,9 +16,12 @@ use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
use Chill\PersonBundle\Form\HouseholdCompositionType;
use Chill\PersonBundle\Repository\Household\HouseholdCompositionRepository;
use Chill\PersonBundle\Repository\Household\HouseholdRepository;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -31,7 +34,7 @@ use Symfony\Component\Security\Core\Security;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class HouseholdCompositionController
class HouseholdCompositionController extends AbstractController
{
private EngineInterface $engine;
@@ -41,6 +44,8 @@ class HouseholdCompositionController
private HouseholdCompositionRepository $householdCompositionRepository;
private HouseholdRepository $householdRepository;
private PaginatorFactory $paginatorFactory;
private Security $security;
@@ -52,6 +57,7 @@ class HouseholdCompositionController
public function __construct(
Security $security,
HouseholdCompositionRepository $householdCompositionRepository,
HouseholdRepository $householdRepository,
PaginatorFactory $paginatorFactory,
FormFactoryInterface $formFactory,
EntityManagerInterface $entityManager,
@@ -67,6 +73,59 @@ class HouseholdCompositionController
$this->translator = $translator;
$this->engine = $engine;
$this->urlGenerator = $urlGenerator;
$this->householdRepository = $householdRepository;
}
/**
* @Route("/{_locale}/person/household/{household_id}/composition/{composition_id}/delete", name="chill_person_household_composition_delete")
*
* @param mixed $household_id
* @param mixed $composition_id
*/
public function deleteAction(Request $request, $household_id, $composition_id): Response
{
$composition = $this->householdCompositionRepository->find($composition_id);
$household = $this->householdRepository->find($household_id);
$this->denyAccessUnlessGranted(HouseholdVoter::EDIT, $household);
if (null === $composition) {
throw $this->createNotFoundException('Unable to find composition entity.');
}
$form = $this->createFormBuilder()
->setAction($this->generateUrl('chill_person_household_composition_delete', [
'composition_id' => $composition_id,
'household_id' => $household_id,
]))
->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
if ($request->getMethod() === Request::METHOD_DELETE) {
$form->handleRequest($request);
if ($form->isValid()) {
$this->entityManager->remove($composition);
$this->entityManager->flush();
$this->addFlash('success', $this->translator
->trans('The composition has been successfully removed.'));
return $this->redirectToRoute('chill_person_household_composition_index', [
'id' => $household_id,
]);
}
}
return $this->render(
'ChillPersonBundle:HouseholdComposition:delete.html.twig',
[
'household' => $household,
'composition' => $composition,
'form' => $form->createView(),
]
);
}
/**

View File

@@ -133,7 +133,11 @@ class AccompanyingPeriod implements
* @ORM\Column(type="date", nullable=true)
* @Groups({"read", "write", "docgen:read"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED})
* @Assert\GreaterThan(propertyPath="openingDate", groups={AccompanyingPeriod::STEP_CLOSED})
* @Assert\GreaterThanOrEqual(
* propertyPath="openingDate",
* groups={AccompanyingPeriod::STEP_CLOSED},
* message="The closing date must be later than the date of creation"
* )
*/
private ?DateTime $closingDate = null;
@@ -213,8 +217,8 @@ class AccompanyingPeriod implements
*
* @ORM\Column(type="date")
* @Groups({"read", "write", "docgen:read"})
* @Assert\LessThan(value="today", groups={AccompanyingPeriod::STEP_CONFIRMED})
* @Assert\LessThan(propertyPath="closingDate", groups={AccompanyingPeriod::STEP_CONFIRMED})
* @Assert\LessThan(value="tomorrow", groups={AccompanyingPeriod::STEP_CONFIRMED})
* @Assert\LessThanOrEqual(propertyPath="closingDate", groups={AccompanyingPeriod::STEP_CONFIRMED})
*/
private ?DateTime $openingDate = null;

View File

@@ -44,7 +44,8 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
{
/**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "read:accompanyingPeriodWork:light"})
* @Serializer\Context(normalizationContext={"groups": {"read"}}, groups={"read:accompanyingPeriodWork:light"})
*/
private ?AccompanyingPeriod $accompanyingPeriod = null;
@@ -63,26 +64,26 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
/**
* @ORM\Column(type="datetime_immutable")
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
*/
private ?DateTimeImmutable $createdAt = null;
/**
* @ORM\Column(type="boolean")
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
*/
private bool $createdAutomatically = false;
/**
* @ORM\Column(type="text")
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
*/
private string $createdAutomaticallyReason = '';
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
*/
private ?User $createdBy = null;
@@ -90,7 +91,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"accompanying_period_work:create"})
* @Serializer\Groups({"accompanying_period_work:edit"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
* @Assert\GreaterThan(propertyPath="startDate",
* message="accompanying_course_work.The endDate should be greater than the start date"
* )
@@ -122,7 +123,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light", "read:evaluation:include-work"})
*/
private ?int $id = null;
@@ -135,7 +136,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
/**
* @ORM\ManyToMany(targetEntity=Person::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_person")
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
* @Serializer\Groups({"accompanying_period_work:edit"})
* @Serializer\Groups({"accompanying_period_work:create"})
*/
@@ -151,8 +152,9 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
/**
* @ORM\ManyToOne(targetEntity=SocialAction::class)
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
* @Serializer\Groups({"accompanying_period_work:create"})
* @Serializer\Context(normalizationContext={"groups": {"read"}}, groups={"read:accompanyingPeriodWork:light"})
*/
private ?SocialAction $socialAction = null;
@@ -160,7 +162,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
* @ORM\Column(type="date_immutable")
* @Serializer\Groups({"accompanying_period_work:create"})
* @Serializer\Groups({"accompanying_period_work:edit"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
*/
private ?DateTimeImmutable $startDate = null;

View File

@@ -39,6 +39,8 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* targetEntity=AccompanyingPeriodWork::class,
* inversedBy="accompanyingPeriodWorkEvaluations"
* )
* @Serializer\Groups({"read:evaluation:include-work"})
* @Serializer\Context(normalizationContext={"groups": {"read:accompanyingPeriodWork:light"}}, groups={"read:evaluation:include-work"})
*/
private ?AccompanyingPeriodWork $accompanyingPeriodWork = null;

View File

@@ -13,7 +13,10 @@ namespace Chill\PersonBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Workflow\Registry;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -24,15 +27,18 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{
protected Registry $registry;
protected Security $security;
/**
* @var TranslatorInterface
*/
protected $translator;
public function __construct(TranslatorInterface $translator, Registry $registry)
public function __construct(TranslatorInterface $translator, Registry $registry, Security $security)
{
$this->translator = $translator;
$this->registry = $registry;
$this->security = $security;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
@@ -47,38 +53,44 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
], ])
->setExtras(['order' => 10]);
$menu->addChild($this->translator->trans('Edit Accompanying Course'), [
'route' => 'chill_person_accompanying_course_edit',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 20]);
if ($this->security->isGranted(AccompanyingPeriodVoter::EDIT, $period)) {
$menu->addChild($this->translator->trans('Edit Accompanying Course'), [
'route' => 'chill_person_accompanying_course_edit',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 20]);
}
if (AccompanyingPeriod::STEP_DRAFT === $period->getStep()) {
// no more menu items if the period is draft
return;
}
$menu->addChild($this->translator->trans('Accompanying Course History'), [
'route' => 'chill_person_accompanying_course_history',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 30]);
if ($this->security->isGranted(AccompanyingPeriodVoter::SEE_DETAILS, $period)) {
$menu->addChild($this->translator->trans('Accompanying Course History'), [
'route' => 'chill_person_accompanying_course_history',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 30]);
$menu->addChild($this->translator->trans('Accompanying Course Action'), [
'route' => 'chill_person_accompanying_period_work_list',
'routeParameters' => [
'id' => $period->getId(),
], ])
->setExtras(['order' => 40]);
$menu->addChild($this->translator->trans('Accompanying Course Comment'), [
'route' => 'chill_person_accompanying_period_comment_list',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 50]);
}
$menu->addChild($this->translator->trans('Accompanying Course Comment'), [
'route' => 'chill_person_accompanying_period_comment_list',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 50]);
if ($this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $period)) {
$menu->addChild($this->translator->trans('Accompanying Course Action'), [
'route' => 'chill_person_accompanying_period_work_list',
'routeParameters' => [
'id' => $period->getId(),
], ])
->setExtras(['order' => 40]);
}
$workflow = $this->registry->get($period, 'accompanying_period_lifecycle');

View File

@@ -16,17 +16,22 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
final class AccompanyingPeriodWorkRepository implements ObjectRepository
{
private EntityManagerInterface $em;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
$this->repository = $entityManager->getRepository(AccompanyingPeriodWork::class);
}
@@ -78,6 +83,36 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository
return $this->repository->findByAccompanyingPeriod($period, $orderBy, $limit, $offset);
}
/**
* Return a list of accompanying period with a defined order:.
*
* * first, opened works
* * then, closed works
*
* @return AccompanyingPeriodWork[]
*/
public function findByAccompanyingPeriodOpenFirst(AccompanyingPeriod $period, int $limit = 10, int $offset = 0): array
{
$rsm = new ResultSetMappingBuilder($this->em);
$rsm->addRootEntityFromClassMetadata(AccompanyingPeriodWork::class, 'w');
$sql = "SELECT {$rsm} FROM chill_person_accompanying_period_work w
WHERE accompanyingPeriod_id = :periodId
ORDER BY
CASE WHEN enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC,
startdate DESC,
enddate DESC,
id DESC
LIMIT :limit OFFSET :offset";
$nq = $this->em->createNativeQuery($sql, $rsm)
->setParameter('periodId', $period->getId(), Types::INTEGER)
->setParameter('limit', $limit, Types::INTEGER)
->setParameter('offset', $offset, Types::INTEGER);
return $nq->getResult();
}
public function findNearEndDateByUser(User $user, DateTimeImmutable $since, DateTimeImmutable $until, int $limit = 20, int $offset = 0): array
{
return $this->buildQueryNearEndDateByUser($user, $since, $until)

View File

@@ -60,14 +60,14 @@
<template v-slot:body>
<p>{{ $t('confirm.sure_description') }}</p>
<div v-if="accompanyingCourse.user === null">
<div v-if="filteredReferrersSuggested.length === 0">
<div v-if="usersSuggestedFilteredByJob.length === 0">
<p class="alert alert-warning">{{ $t('confirm.no_suggested_referrer') }}</p>
</div>
<div v-if="filteredReferrersSuggested.length === 1" class="alert alert-info">
<div v-if="usersSuggestedFilteredByJob.length === 1" class="alert alert-info">
<p>{{ $t('confirm.one_suggested_referrer') }}:</p>
<ul class="list-suggest add-items inline">
<li>
<user-render-box-badge :user="filteredReferrersSuggested[0]"></user-render-box-badge>
<user-render-box-badge :user="usersSuggestedFilteredByJob[0]"></user-render-box-badge>
</li>
</ul>
<p>{{ $t('confirm.choose_suggested_referrer') }}</p>
@@ -150,7 +150,6 @@ export default {
computed: {
...mapState({
accompanyingCourse: state => state.accompanyingCourse,
filteredReferrersSuggested: state => state.filteredReferrersSuggested
}),
...mapGetters([
'isParticipationValid',
@@ -160,15 +159,16 @@ export default {
'isLocationValid',
'isJobValid',
'validationKeys',
'isValidToBeConfirmed'
'isValidToBeConfirmed',
'usersSuggestedFilteredByJob',
]),
deleteLink() {
return `/fr/parcours/${this.accompanyingCourse.id}/delete`; //TODO locale
},
disableConfirm() {
return this.clickedDoNotChooseReferrer
? (this.accompanyingCourse.user === null && this.filteredReferrersSuggested.length === 0)
: (this.accompanyingCourse.user === null && this.filteredReferrersSuggested.length === 0) || (this.filteredReferrersSuggested.length === 1);
return (this.accompanyingCourse.user === null
&& this.usersSuggestedFilteredByJob.length === 1
&& this.clickedDoNotChooseReferrer === false);
}
},
methods: {
@@ -183,7 +183,7 @@ export default {
});
},
chooseSuggestedReferrer() {
this.$store.dispatch('updateReferrer', this.filteredReferrersSuggested[0])
this.$store.dispatch('updateReferrer', this.usersSuggestedFilteredByJob[0])
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));

View File

@@ -1,164 +1,178 @@
<template>
<div class="vue-component">
<h2><a id="section-80"></a>{{ $t('referrer.title') }}</h2>
<div class="vue-component">
<h2><a id="section-80"></a>{{ $t('referrer.title') }}</h2>
<div>
<div>
<label class="col-form-label" for="selectJob">
{{ $t('job.label') }}
{{ $t('job.label') }}
</label>
<VueMultiselect
name="selectJob"
label="text"
:custom-label="customJobLabel"
track-by="id"
:multiple="false"
:searchable="true"
:placeholder="$t('job.placeholder')"
v-model="valueJob"
:options="jobs"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
@select="updateJob">
</VueMultiselect>
name="selectJob"
label="text"
:custom-label="customJobLabel"
track-by="id"
:multiple="false"
:searchable="true"
:placeholder="$t('job.placeholder')"
v-model="valueJob"
:options="jobs"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
></VueMultiselect>
<label class="col-form-label" for="selectReferrer">
{{ $t('referrer.label') }}
{{ $t('referrer.label') }}
</label>
<VueMultiselect
name="selectReferrer"
label="text"
track-by="id"
:multiple="false"
:searchable="true"
:placeholder="$t('referrer.placeholder')"
v-model="value"
:options="users"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
@select="updateReferrer">
</VueMultiselect>
name="selectReferrer"
label="text"
track-by="id"
:multiple="false"
:searchable="true"
:placeholder="$t('referrer.placeholder')"
v-model="value"
:options="users"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
></VueMultiselect>
<template v-if="filteredReferrersSuggested.length > 0">
<ul class="list-suggest add-items inline">
<li v-for="(u, i) in filteredReferrersSuggested" @click="updateReferrer(u)" :key="`referrer-${i}`">
<template v-if="usersSuggestedFilteredByJob.length > 0">
<ul class="list-suggest add-items inline">
<li v-for="(u, i) in usersSuggestedFilteredByJob" @click="updateReferrer(u)" :key="`referrer-${i}`">
<span>
<user-render-box-badge :user="u"></user-render-box-badge>
</span>
</li>
</li>
</ul>
</template>
</div>
</div>
<div>
<div>
<ul class="record_actions">
<li>
<button
class="btn btn-create"
type="button"
name="button"
@click="assignMe">
{{ $t('referrer.assign_me') }}
</button>
</li>
<li>
<button
class="btn btn-create"
type="button"
name="button"
@click="assignMe">
{{ $t('referrer.assign_me') }}
</button>
</li>
</ul>
</div>
</div>
<div v-if="!isJobValid" class="alert alert-warning to-confirm">
<div v-if="!isJobValid" class="alert alert-warning to-confirm">
{{ $t('job.not_valid') }}
</div>
</div>
</div>
</div>
</template>
<script>
import VueMultiselect from 'vue-multiselect';
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
import { mapState, mapGetters } from 'vuex';
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods';
import {mapState, mapGetters} from 'vuex';
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge";
export default {
name: "Referrer",
components: {
UserRenderBoxBadge,
VueMultiselect,
},
data() {
return {
jobs: []
name: "Referrer",
components: {
UserRenderBoxBadge,
VueMultiselect,
},
data() {
return {
jobs: []
}
},
computed: {
...mapState({
valueJob: state => state.accompanyingCourse.job,
}),
...mapGetters(['isJobValid', 'usersSuggestedFilteredByJob']),
users: function () {
let users = this.$store.getters.usersFilteredByJob;
console.log('users filtered by job', users);
// ensure that the selected user is in the list. add it if necessary
if (this.$store.state.accompanyingCourse.user !== null && users.find(u => this.$store.state.accompanyingCourse.user.id === u.id) === undefined) {
console.log('add user to users');
users.push(this.$store.state.accompanyingCourse.user);
}
},
computed: {
...mapState({
value: state => state.accompanyingCourse.user,
valueJob: state => state.accompanyingCourse.job,
users: state => state.users.filter(u => {
if (u.user_job && state.accompanyingCourse.job) {
return u.user_job.id === state.accompanyingCourse.job.id;
} else {
return false;
}
}),
filteredReferrersSuggested: state => state.filteredReferrersSuggested,
}),
...mapGetters([
'isJobValid'
])
},
mounted() {
this.getJobs();
},
methods: {
updateReferrer(value) {
this.$store.dispatch('updateReferrer', value)
console.log('users to return', users);
return users;
},
valueJob: {
get() {
return this.$store.state.accompanyingCourse.job;
},
set(value) {
this.$store.dispatch('updateJob', value)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
},
getJobs() {
const url = '/api/1.0/main/user-job.json';
makeFetch('GET', url)
.then(response => {
this.jobs = response.results;
})
.catch((error) => {
this.$toast.open({message: error.txt})
})
},
updateJob(value) {
this.$store.dispatch('updateJob', value)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
},
customJobLabel(value) {
return value.label.fr;
},
assignMe() {
const url = `/api/1.0/main/whoami.json`;
makeFetch('GET', url)
.then(response => {
this.$store.dispatch('updateReferrer', response);
return response;
})
.catch((error) => {
commit('catchError', error);
this.$toast.open({message: error.body})
})
}
}
},
value: {
get() {
return this.$store.state.accompanyingCourse.user;
},
set(value) {
console.log('set referrer', value);
this.$store.dispatch('updateReferrer', value)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}
},
},
mounted() {
this.getJobs();
},
methods: {
updateReferrer(value) {
this.value = value;
},
getJobs() {
const url = '/api/1.0/main/user-job.json';
makeFetch('GET', url)
.then(response => {
this.jobs = response.results;
})
.catch((error) => {
this.$toast.open({message: error.txt})
})
},
customJobLabel(value) {
return value.label.fr;
},
assignMe() {
const url = `/api/1.0/main/whoami.json`;
makeFetch('GET', url)
.then(user => {
this.value = user
})
/*.catch((error) => {
commit('catchError', error);
this.$toast.open({message: error.body})
})*/
}
}
}
</script>

View File

@@ -140,7 +140,7 @@ const appMessages = {
sure_description: "Une fois le changement confirmé, il ne sera plus possible de le remettre à l'état de brouillon !",
ok: "Confirmer le parcours",
delete: "Supprimer le parcours",
no_suggested_referrer: "Il n'y a aucun référent qui puisse être désigné pour ce parcours. Vérifiez la localisation du parcours, les métiers et service indiqués. Si le problème persiste, contactez l'administrateur du logiciel.",
no_suggested_referrer: "Il n'y a aucun référent qui puisse être suggéré pour ce parcours. Vérifiez la localisation du parcours, les métiers et service indiqués. Si les données sont correctes, vous pouvez confirmer ce parcours.",
one_suggested_referrer: "Un unique référent peut être suggéré pour ce parcours",
choose_suggested_referrer: "Voulez-vous le désigner directement ?",
choose_button: "Désigner",

View File

@@ -40,7 +40,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
scopesAtBackend: accompanyingCourse.scopes.map(scope => scope),
// the users which are available for referrer
referrersSuggested: [],
filteredReferrersSuggested: [],
// all the users available
users: [],
permissions: {}
@@ -93,6 +92,30 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
return false;
},
usersFilteredByJob(state) {
return state.users.filter(u => {
if (null !== state.accompanyingCourse.job) {
return (u.user_job === null ? null : u.user_job.id) === state.accompanyingCourse.job.id;
} else {
return true;
}
});
},
usersSuggestedFilteredByJob(state) {
return state.referrersSuggested.filter(u => {
if (null !== state.accompanyingCourse.job) {
return (u.user_job === null ? null : u.user_job.id) === state.accompanyingCourse.job.id;
} else {
return true;
}
}).filter(u => {
if (null !== state.accompanyingCourse.user) {
return u.id !== state.accompanyingCourse.user.id;
}
return true;
});
},
},
mutations: {
catchError(state, error) {
@@ -216,22 +239,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
return u;
});
},
setFilteredReferrersSuggested(state) {
state.filteredReferrersSuggested = state.referrersSuggested.filter(u => {
if (u.user_job && state.accompanyingCourse.job && state.accompanyingCourse.user) {
return u.user_job.id === state.accompanyingCourse.job.id && state.accompanyingCourse.user.id !== u.id
} else {
if (null === state.accompanyingCourse.user) {
if (u.user_job && state.accompanyingCourse.job) {
return u.user_job.id === state.accompanyingCourse.job.id
} else {
return true;
}
}
return state.accompanyingCourse.user.id !== u.id;
}
})
},
confirmAccompanyingCourse(state, response) {
//console.log('### mutation: confirmAccompanyingCourse: response', response);
state.accompanyingCourse.step = response.step;
@@ -689,8 +696,14 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
})
},
updateReferrer({ commit }, payload) {
console.log('update referrer', payload);
console.log('payload !== null', payload !== null);
const url = `/api/1.0/person/accompanying-course/${id}.json`;
const body = { type: "accompanying_period", user: { id: payload.id, type: payload.type }};
let body = { type: "accompanying_period", user: null };
if (payload !== null) {
body = { type: "accompanying_period", user: { id: payload.id, type: payload.type } };
}
return makeFetch('PATCH', url, body)
.then((response) => {
@@ -704,12 +717,15 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
},
updateJob({ commit }, payload) {
const url = `/api/1.0/person/accompanying-course/${id}.json`;
const body = { type: "accompanying_period", job: { id: payload.id, type: payload.type }};
let body = { type: "accompanying_period", job: null };
if (payload !== null) {
body = { type: "accompanying_period", job: { id: payload.id, type: payload.type } };
}
return makeFetch('PATCH', url, body)
.then((response) => {
commit('updateJob', response.job);
commit('setFilteredReferrersSuggested');
})
.catch((error) => {
commit('catchError', error);
@@ -734,7 +750,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
async fetchReferrersSuggested({ state, commit}) {
let users = await getReferrersSuggested(state.accompanyingCourse);
commit('setReferrersSuggested', users);
commit('setFilteredReferrersSuggested');
if (
null === state.accompanyingCourse.user
&& !state.accompanyingCourse.confidential

View File

@@ -92,7 +92,8 @@
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
:id="evaluation.id"
:templates="getTemplatesAvailables"
:beforeMove="submitBeforeGenerate"
:preventDefaultMoveToGenerate="true"
@go-to-generate-document="submitBeforeGenerate"
>
<template v-slot:title>
<label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
@@ -109,6 +110,7 @@ import CKEditor from '@ckeditor/ckeditor5-vue';
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
import { mapGetters, mapState } from 'vuex';
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
const i18n = {
messages: {
@@ -123,7 +125,7 @@ const i18n = {
evaluation_public_comment: "Note publique",
evaluation_comment_placeholder: "Commencez à écrire ...",
evaluation_generate_a_document: "Générer un document",
evaluation_choose_a_template: "Choisir un gabarit",
evaluation_choose_a_template: "Choisir un modèle",
evaluation_add_a_document: "Ajouter un document",
evaluation_add: "Ajouter une évaluation",
Documents: "Documents",
@@ -207,10 +209,11 @@ export default {
return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent(
window.location.pathname + window.location.search + window.location.hash);
},
submitBeforeGenerate() {
submitBeforeGenerate({template}) {
const callback = (data) => {
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
return Promise.resolve({entityId: evaluationId});
window.location.assign(buildLink(template, evaluationId, 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation'));
};
return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; });

View File

@@ -195,18 +195,18 @@ export default {
setTimeout(function() {
if (query === "") {
this.loadSuggestions([]);
return;
this.loadSuggestions([]);
return;
}
if (query === this.search.query) {
if (this.currentSearchQueryController !== undefined) {
this.currentSearchQueryController.abort()
}
this.currentSearchQueryController = new AbortController();
searchEntities({ query, options: this.options }, this.currentSearchQueryController)
if (this.currentSearchQueryController !== undefined) {
this.currentSearchQueryController.abort()
}
this.currentSearchQueryController = new AbortController();
searchEntities({ query, options: this.options }, this.currentSearchQueryController)
.then(suggested => new Promise((resolve, reject) => {
this.loadSuggestions(suggested.results);
resolve();
this.loadSuggestions(suggested.results);
resolve();
}));
}
}.bind(this), query.length > 3 ? 300 : 700);
@@ -241,13 +241,12 @@ export default {
return item.result.type + item.result.id;
},
addPriorSuggestion() {
//console.log('addPriorSuggestion', this.hasPriorSuggestion);
// console.log('prior suggestion', this.priorSuggestion);
if (this.hasPriorSuggestion) {
console.log('addPriorSuggestion',);
// console.log('addPriorSuggestion',);
this.suggested.unshift(this.priorSuggestion);
this.selected.unshift(this.priorSuggestion);
console.log('reset priorSuggestion');
this.newPriorSuggestion(null);
}
},
@@ -260,13 +259,14 @@ export default {
result: entity
}
this.search.priorSuggestion = suggestion;
console.log('search priorSuggestion', this.search.priorSuggestion);
// console.log('search priorSuggestion', this.search.priorSuggestion);
this.addPriorSuggestion(suggestion);
} else {
this.search.priorSuggestion = {};
}
},
saveFormOnTheFly({ type, data }) {
console.log('saveFormOnTheFly from addPersons, type', type, ', data', data);
// console.log('saveFormOnTheFly from addPersons, type', type, ', data', data);
if (type === 'person') {
makeFetch('POST', '/api/1.0/person/person.json', data)
.then(response => {
@@ -276,10 +276,10 @@ export default {
.catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
this.$toast.open({message: v });
}
} else {
this.$toast.open({message: 'An error occurred'});
this.$toast.open({message: 'An error occurred'});
}
})
}
@@ -292,13 +292,14 @@ export default {
.catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
this.$toast.open({message: v });
}
} else {
this.$toast.open({message: 'An error occurred'});
this.$toast.open({message: 'An error occurred'});
}
})
}
this.canCloseOnTheFlyModal = false;
}
},
}

View File

@@ -23,6 +23,8 @@
</div>
{% endif %}
{{ chill_pagination(paginator) }}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_new', { 'id': accompanyingCourse.id }) }}"

View File

@@ -41,7 +41,7 @@
{% endif %}
{% endif %}
</div>
<div class="wh-col">
<div class="wh-col" style="align-items: center;">
{% if chill_accompanying_periods.fields.user == 'visible' %}
{# the tags `data-referrer-text` is used by module `@ChillPerson/mod/AccompanyingPeriod/setReferrer.js` #}
{% if period.user %}

View File

@@ -0,0 +1,38 @@
{% extends '@ChillPerson/Household/layout.html.twig' %}
{% set activeRouteKey = 'chill_person_household_composition_index' %}
{% block title 'Remove household composition'|trans %}
{% block display_content %}
<p>{{ 'Concerns household n°%id%'|trans({ '%id%' : household.id } ) }}</p>
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Composition'|trans }}:</h3>
</div>
<div class="wl-col list">
{% for m in household.members %}
<span class="wl-item">
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: m.person.id },
buttonText: m.person|chill_entity_render_string,
isDead: m.person.deathdate is not null
} %}
</span>
{% endfor %}
</div>
</div>
{% endblock %}
{% block content %}
{{ include('@ChillMain/Util/confirmation_template.html.twig',
{
'title' : 'Remove household composition'|trans,
'confirm_question' : 'Are you sure you want to remove this composition?'|trans,
'display_content' : block('display_content'),
'cancel_route' : 'chill_person_household_composition_index',
'cancel_parameters' : { 'composition_id' : composition.id, 'id' : household.id },
'form' : form
} ) }}
{% endblock %}

View File

@@ -22,7 +22,7 @@
<h3>{{ c.householdCompositionType.label|localize_translatable_string }}</h3>
<p>{{ 'household_composition.numberOfChildren'|trans }}: {{ c.numberOfChildren }}</p>
</div>
<div class="item-col">{{ 'household_composition.Since'|trans({'startDate': c.startDate}) }}</div>
<div class="item-col" style="justify-content: flex-end">{{ 'household_composition.Since'|trans({'startDate': c.startDate}) }}</div>
</div>
<div class="item-row">
<div class="item-col">
@@ -45,7 +45,10 @@
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id, 'edit': c.id}) }}" class="btn btn-edit"></a>
</li>
<li>
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id, 'edit': c.id}) }}" class="btn btn-edit"></a>
<a href="{{ chill_path_add_return_path('chill_person_household_composition_delete', {'composition_id': c.id,
'household_id': c.household.id}) }}"
class="btn btn-delete"
title="{{ 'Delete'|trans }}"></a>
</li>
</ul>
</div>
@@ -57,8 +60,8 @@
{{ form_widget(form) }}
<ul class="record_actions">
<li class="cancel">
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id}) }}">{{ 'Cancel'|trans }}</a>
<li class="cancel" style="margin-right: auto;">
<a class="btn btn-cancel" href="{{ path('chill_person_household_composition_index', {'id': c.household.id}) }}">{{ 'Cancel'|trans }}</a>
</li>
<li>
<button type="submit" class="btn btn-create">{{ 'Save'|trans }}</button>

View File

@@ -85,9 +85,11 @@
</div>
<div id="maritalStatusDate">
{{ form_row(form.maritalStatusDate, { 'label' : 'Date of last marital status change'} ) }}
{{ form_row(form.maritalStatusComment, { 'label' : 'Comment on the marital status'} ) }}
</div>
{%- endif -%}
<div id="maritalStatusComment">
{{ form_row(form.maritalStatusComment, { 'label' : 'Comment on the marital status'} ) }}
</div>
</fieldset>
{%- endif -%}

View File

@@ -99,11 +99,12 @@
{% endif %}
</div>
<div class="ms-auto">
{% if acp.requestorPerson == person %}
{% if acp.requestoranonymous == false and acp.requestorPerson == person %}
<span class="as-requestor badge bg-info" title="{{ 'Requestor'|trans|e('html_attr') }}">
{{ 'Requestor'|trans({'gender': person.gender}) }}
</span>
{% endif %}
{% if acp.emergency %}
<span class="badge rounded-pill bg-danger">{{- 'Emergency'|trans|upper -}}</span>
{% endif %}
@@ -178,47 +179,49 @@
</div>
{% endif %}
{% if acp.requestoranonymous == false %}
{% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %}
<div class="wl-row">
<div class="wl-col title">
<h3>
{% if acp.requestorPerson is not null %}
{{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }}
{% else %}
{{ 'Requestor'|trans({'gender': 'other'})}}
{% endif %}
</h3>
</div>
<div class="wl-col list">
{% if acp.requestorThirdParty is not null %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id },
action: 'show',
displayBadge: true,
buttonText: acp.requestorThirdParty|chill_entity_render_string
} %}
{% else %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: acp.requestorPerson.id },
action: 'show',
displayBadge: true,
buttonText: acp.requestorPerson|chill_entity_render_string,
isDead: acp.requestorPerson.deathdate is not null
} %}
{% endif %}
</div>
</div>
{% endif %}
{% endif %}
<ul class="record_actions record_actions_column">
<li>
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': acp.id }) }}"
class="btn btn-sm btn-outline-primary" title="{{ 'See accompanying period'|trans }}">
class="btn btn-sm btn-outline-primary" title="{{ 'See accompanying period'|trans }}">
<i class="fa fa-random fa-fw"></i>
</a>
</li>
</ul>
{% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %}
<div class="wl-row">
<div class="wl-col title">
<h3>
{% if acp.requestorPerson is not null %}
{{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }}
{% else %}
{{ 'Requestor'|trans({'gender': 'other'})}}
{% endif %}
</h3>
</div>
<div class="wl-col list">
{% if acp.requestorThirdParty is not null %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id },
action: 'show',
displayBadge: true,
buttonText: acp.requestorThirdParty|chill_entity_render_string
} %}
{% else %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: acp.requestorPerson.id },
action: 'show',
displayBadge: true,
buttonText: acp.requestorPerson|chill_entity_render_string,
isDead: acp.requestorPerson.deathdate is not null
} %}
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% endfor %}

View File

@@ -23,18 +23,27 @@
{% for resource in personResources %}
<div class="item-bloc">
<div class="item-row">
<div class="item-col">
<div class="item-col" style="width: 50%">
{% if resource.person is not null %}
<div class="denomination h3">
<span class="name">{{ resource.person }}</span>
<span class="badge rounded-pill bg-person">{{ 'person'|trans|capitalize }}</span>
<span>
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: resource.person.id },
buttonText: resource.person|chill_entity_render_string,
isDead: resource.person.deathdate is not null
} %}
</span>
</div>
{% elseif resource.thirdparty is not null %}
<div class="denomination h3">
<span class="name">{{ resource.thirdparty }}</span>
<span class="badge rounded-pill bg-thirdparty">
{{ 'thirdparty'|trans|capitalize }}
<i class="fa fa-fw fa-user-md"></i>
<span>
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'thirdparty', id: resource.thirdparty.id },
buttonText: resource.thirdParty|chill_entity_render_string,
parent: resource.thirdparty.parent
} %}
</span>
</div>
{% else %}
@@ -43,7 +52,7 @@
</div>
{% endif %}
</div>
<div class="item-col">
<div class="item-col" style="justify-content: flex-end; ">
{% if resource.kind %}
<span>{{ resource.kind.title.fr|capitalize }}</span>
{% endif %}
@@ -56,7 +65,7 @@
</section>
</div>
{% endif %}
{% if is_granted('CHILL_PERSON_UPDATE', resource.person) %}
{% if is_granted('CHILL_PERSON_UPDATE', resource.personOwner) %}
<div class="item-row separator">
<div class="item-col">
<ul class="record_actions">

View File

@@ -11,17 +11,23 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Security\Authorization;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use UnexpectedValueException;
use function get_class;
use function in_array;
class AccompanyingPeriodWorkVoter extends Voter
{
public const CREATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE';
public const SEE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE';
public const UPDATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE';
private Security $security;
public function __construct(Security $security)
@@ -31,8 +37,14 @@ class AccompanyingPeriodWorkVoter extends Voter
protected function supports($attribute, $subject): bool
{
return $subject instanceof AccompanyingPeriodWork
&& in_array($attribute, $this->getRoles(), true);
return
(
$subject instanceof AccompanyingPeriodWork
&& in_array($attribute, $this->getRoles(), true)
) || (
$subject instanceof AccompanyingPeriod
&& in_array($attribute, [self::SEE, self::CREATE], true)
);
}
/**
@@ -41,13 +53,28 @@ class AccompanyingPeriodWorkVoter extends Voter
*/
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{
switch ($attribute) {
case self::SEE:
return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod());
if ($subject instanceof AccompanyingPeriodWork) {
switch ($attribute) {
case self::SEE:
return $this->security->isGranted(AccompanyingPeriodVoter::SEE_DETAILS, $subject->getAccompanyingPeriod());
default:
throw new UnexpectedValueException("attribute {$attribute} is not supported");
default:
throw new UnexpectedValueException("attribute {$attribute} is not supported");
}
} elseif ($subject instanceof AccompanyingPeriod) {
switch ($attribute) {
case self::SEE:
return $this->security->isGranted(AccompanyingPeriodVoter::SEE_DETAILS, $subject);
default:
throw new UnexpectedValueException(sprintf(
"attribute {$attribute} is not supported on instance %s",
AccompanyingPeriod::class
));
}
}
throw new UnexpectedValueException(sprintf("attribute {$attribute} on instance %s is not supported", get_class($subject)));
}
private function getRoles(): array

View File

@@ -109,7 +109,6 @@ final class AccompanyingPeriodConfidentialTest extends WebTestCase
$user = new stdClass();
$user->id = 0;
$user->type = 'user';
dump($user);
$this->client->request(
Request::METHOD_PATCH,

View File

@@ -36,7 +36,7 @@ class HouseholdMembershipSequentialValidator extends ConstraintValidator
public function validate($person, Constraint $constraint)
{
if (!$person instanceof Person) {
throw new UnexpectedTypeException($constraint, Person::class);
throw new UnexpectedTypeException($person, Person::class);
}
$participations = $person->getHouseholdParticipationsShareHousehold();

View File

@@ -148,7 +148,7 @@ and %number% other: '{0} et aucun autre| {1} et une autre |]1, Inf] et %number%
'Last opening since %last_opening%': 'Dernière ouverture le %last_opening%.'
'Person accompanying period - %name%': 'Historique du dossier - %name%'
'Opening date': 'Date d''ouverture'
'Closing date': 'Date de fermeture'
'Closing date': 'Date de clôture'
'Period opened': 'Période ouverte'
'Close accompanying period': 'Clôre la période'
'Open accompanying period': 'Ouvrir la période'
@@ -211,7 +211,7 @@ No requestor: Pas de demandeur
No resources: "Pas d'interlocuteurs privilégiés"
Persons associated: Usagers concernés
Referrer: Référent
Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur appartenance à un ménage dès que possible.
Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur ménage dès que possible.
Add to household now: Ajouter à un ménage
Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours
course.draft: Brouillon
@@ -237,6 +237,7 @@ no comment found: "Aucun commentaire"
Select a type: "Choisissez un type"
Select a person: "Choisissez un usager"
Select a thirdparty: "Choisissez un tiers"
Contact person: "Personne de contact"
# pickAPersonType
@@ -486,6 +487,10 @@ Household summary: Résumé du ménage
Edit household address: Modifier l'adresse du ménage
Show household: Voir le ménage
Back to household: Revenir au ménage
Remove household composition: Supprimer composition familiale
Are you sure you want to remove this composition?: Etes-vous sûr de vouloir supprimer cette composition familiale ?
Concerns household n°%id%: Concerne le ménage n°%id%
Composition: Composition
# accompanying course work
Accompanying Course Actions: Actions d'accompagnements
@@ -552,7 +557,7 @@ household_composition:
numberOfChildren: Nombre d'enfants mineurs au sein du ménage
Household composition: Composition du ménage
Composition added: Information sur la composition familiale ajoutée
Currently no composition: Aucune composition famiale renseignée.
Currently no composition: Aucune composition familiale renseignée.
Add a composition: Ajouter une composition familiale
Update composition: Modifier la composition familiale

View File

@@ -11,6 +11,7 @@
'Closing date is not valid': 'La date de fermeture n''est pas valide'
'Closing date can not be null': 'La date de fermeture ne peut être nulle'
The date of closing is before the date of opening: La période de fermeture est avant la période d'ouverture
The closing date must be later than the date of creation: La date de clôture doit être postérieure à la date de création du parcours
The birthdate must be before %date%: La date de naissance doit être avant le %date%
'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33123456789': 'Numéro de téléphone invalide: il doit commencer par le préfixe international précédé de "+", ne comporter que des chiffres et faire moins de 20 caractères. Ex: +31623456789'
'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33623456789': 'Numéro de téléphone invalide: il doit commencer par le préfixe international précédé de "+", ne comporter que des chiffres et faire moins de 20 caractères. Ex: +33623456789'