merge last AddPerson branch

This commit is contained in:
nobohan
2022-02-16 11:23:21 +01:00
105 changed files with 1554 additions and 882 deletions

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\MainBundle\Serializer\Model\Counter;
use Chill\MainBundle\Serializer\Normalizer\DiscriminatedObjectDenormalizer;
use Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestionInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
@@ -234,17 +235,19 @@ final class AccompanyingCourseApiController extends ApiController
$requestor = null;
$exceptions = [];
foreach ([Person::class, ThirdParty::class] as $class) {
try {
$requestor = $this->getSerializer()
->deserialize($request->getContent(), $class, $_format, []);
} catch (RuntimeException $e) {
$exceptions[] = $e;
}
try {
$requestor = $this->getSerializer()->deserialize(
$request->getContent(),
'@multi',
$_format,
[DiscriminatedObjectDenormalizer::ALLOWED_TYPES => [Person::class, ThirdParty::class]]
);
} catch (RuntimeException $e) {
$exceptions[] = $e;
}
if (null === $requestor) {
throw new BadRequestException('Could not find any person or requestor', 0, $exceptions[0]);
throw new BadRequestException('Could not find any person or thirdparty', 0, null);
}
$accompanyingPeriod->setRequestor($requestor);

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

@@ -50,7 +50,6 @@ class HouseholdApiController extends ApiController
*/
public function getHouseholdByAddressReference(AddressReference $addressReference): Response
{
// TODO ACL
$this->denyAccessUnlessGranted('ROLE_USER');
$total = $this->householdACLAwareRepository->countByAddressReference($addressReference);

View File

@@ -224,6 +224,8 @@ final class PersonController extends AbstractController
'label' => 'Add the person',
])->add('createPeriod', SubmitType::class, [
'label' => 'Add the person and create an accompanying period',
])->add('createHousehold', SubmitType::class, [
'label' => 'Add the person and create a household',
]);
$form->handleRequest($request);
@@ -252,6 +254,12 @@ final class PersonController extends AbstractController
]);
}
if ($form->get('createHousehold')->isClicked()) {
return $this->redirectToRoute('chill_person_household_members_editor', [
'persons' => [$person->getId()],
]);
}
return $this->redirectToRoute(
'chill_person_general_edit',
['person_id' => $person->getId()]

View File

@@ -34,16 +34,36 @@ class UserAccompanyingPeriodController extends AbstractController
*/
public function listAction(Request $request)
{
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser()]);
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']]);
$pagination = $this->paginatorFactory->create($total);
$accompanyingPeriods = $this->accompanyingPeriodRepository->findBy(
['user' => $this->getUser()],
['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']],
['openingDate' => 'DESC'],
$pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber()
);
return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [
'accompanyingds' => $accompanyingPeriods,
'pagination' => $pagination,
]);
}
/**
* @Route("/{_locale}/accompanying-periods/drafts", name="chill_person_accompanying_period_draft_user")
*/
public function listDraftsAction(Request $request)
{
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => 'DRAFT']);
$pagination = $this->paginatorFactory->create($total);
$accompanyingPeriods = $this->accompanyingPeriodRepository->findBy(
['createdBy' => $this->getUser(), 'step' => 'DRAFT'],
['id' => 'DESC'],
$pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber()
);
return $this->render('@ChillPerson/AccompanyingPeriod/user_draft_periods_list.html.twig', [
'accompanyingPeriods' => $accompanyingPeriods,
'pagination' => $pagination,
]);

View File

@@ -1153,11 +1153,8 @@ class AccompanyingPeriod implements
$this->removeComment($this->pinnedComment);
}
if ($comment instanceof Comment) {
if (null !== $this->pinnedComment) {
$this->addComment($this->pinnedComment);
}
$this->addComment($comment);
if (null !== $this->pinnedComment) {
$this->addComment($this->pinnedComment);
}
$this->pinnedComment = $comment;

View File

@@ -44,8 +44,8 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
{
/**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
* @Serializer\Groups({"read","read:accompanyingPeriodWork:light"})
* @Serializer\Context(normalizationContext={"groups"={"read"}}, groups={"read:accompanyingPeriodWork:light"})
* @Serializer\Groups({"read", "read:accompanyingPeriodWork:light"})
* @Serializer\Context(normalizationContext={"groups": {"read"}}, groups={"read:accompanyingPeriodWork:light"})
*/
private ?AccompanyingPeriod $accompanyingPeriod = 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

@@ -44,6 +44,13 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
'order' => 20,
'icon' => 'tasks',
]);
$menu->addChild('My accompanying periods in draft', [
'route' => 'chill_person_accompanying_period_draft_user',
])
->setExtras([
'order' => 30,
'icon' => 'tasks',
]);
}
}

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

@@ -14,7 +14,7 @@ namespace Chill\PersonBundle\Repository\Household;
use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
@@ -39,7 +39,7 @@ final class HouseholdACLAwareRepository implements HouseholdACLAwareRepositoryIn
{
$centers = $this->authorizationHelper->getReachableCenters(
$this->security->getUser(),
HouseholdVoter::SEE
PersonVoter::SEE // the authorization to see a household is the same as seeing a person
);
if ([] === $centers) {

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

@@ -20,7 +20,6 @@
<div class="form-check" v-for="p in participationWithoutHousehold" :key="p.id">
<input type="checkbox"
class="form-check-input"
v-model="hasNoHousehold"
name="persons[]"
checked="checked"
:id="p.person.id"
@@ -56,14 +55,18 @@
</div>
<div>
<add-persons
buttonTitle="persons_associated.add_persons"
modalTitle="add_persons.title"
v-bind:key="addPersons.key"
v-bind:options="addPersons.options"
@addNewPersons="addNewPersons"
ref="addPersons"> <!-- to cast child method -->
</add-persons>
<ul class="record_actions">
<li class="add-persons">
<add-persons
buttonTitle="persons_associated.add_persons"
modalTitle="add_persons.title"
v-bind:key="addPersons.key"
v-bind:options="addPersons.options"
@addNewPersons="addNewPersons"
ref="addPersons"> <!-- to cast child method -->
</add-persons>
</li>
</ul>
</div>
<div v-if="!isParticipationValid" class="alert alert-warning to-confirm">

View File

@@ -10,6 +10,7 @@
addAge : true,
hLevel : 3,
isConfidential : false,
isMultiline: true,
}"
:person="participation.person"
:returnPath="getAccompanyingCourseReturnPath">

View File

@@ -1,164 +1,172 @@
<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;
// 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) {
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)
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

@@ -6,7 +6,7 @@
<div v-if="accompanyingCourse.requestor && isAnonymous" class="flex-table">
<label>
<input type="checkbox" v-model="isAnonymous" class="me-2" />
<input type="checkbox" v-model="requestorIsAnonymous" class="me-2" />
{{ $t('requestor.is_anonymous') }}
</label>
<confidential v-if="accompanyingCourse.requestor.type === 'thirdparty'">
@@ -72,7 +72,7 @@
<div v-else-if="accompanyingCourse.requestor && !isAnonymous" class="flex-table">
<label>
<input type="checkbox" v-model="isAnonymous" class="me-2" />
<input type="checkbox" v-model="requestorIsAnonymous" class="me-2" />
{{ $t('requestor.is_anonymous') }}
</label>
@@ -144,14 +144,18 @@
</div>
<div>
<add-persons v-if="accompanyingCourse.requestor === null"
buttonTitle="requestor.add_requestor"
modalTitle="requestor.add_requestor"
v-bind:key="addPersons.key"
v-bind:options="addPersons.options"
@addNewPersons="addNewPersons"
ref="addPersons"> <!-- to cast child method -->
</add-persons>
<ul class="record_actions">
<li class="add-persons">
<add-persons v-if="accompanyingCourse.requestor === null"
buttonTitle="requestor.add_requestor"
modalTitle="requestor.add_requestor"
v-bind:key="addPersons.key"
v-bind:options="addPersons.options"
@addNewPersons="addNewPersons"
ref="addPersons"> <!-- to cast child method -->
</add-persons>
</li>
</ul>
</div>
</div>
@@ -214,7 +218,7 @@ export default {
accompanyingCourse() {
return this.$store.state.accompanyingCourse
},
isAnonymous: {
requestorIsAnonymous: {
set(value) {
this.$store.dispatch('requestorIsAnonymous', value);
},
@@ -283,7 +287,9 @@ export default {
body.name = payload.data.text;
body.email = payload.data.email;
body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id };
if (payload.data.address) {
body.address = { id: payload.data.address.address_id };
}
makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body)
.then(response => {

View File

@@ -29,14 +29,18 @@
</div>
<div>
<add-persons
buttonTitle="resources.add_resources"
modalTitle="resources.add_resources"
v-bind:key="addPersons.key"
v-bind:options="addPersons.options"
@addNewPersons="addNewPersons"
ref="addPersons"> <!-- to cast child method -->
</add-persons>
<ul class="record_actions">
<li class="add-persons">
<add-persons
buttonTitle="resources.add_resources"
modalTitle="resources.add_resources"
v-bind:key="addPersons.key"
v-bind:options="addPersons.options"
@addNewPersons="addNewPersons"
ref="addPersons"> <!-- to cast child method -->
</add-persons>
</li>
</ul>
</div>
</div>

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;
@@ -421,42 +428,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
throw error;
})
},
/**
* On The Fly
*/
patchOnTheFly({ commit }, payload) {
// TODO should be into the dedicated component, no ? JF
console.log('## action: patch OnTheFly', payload);
let body = { type: payload.type };
if (payload.type === 'person') {
body.firstName = payload.data.firstName;
body.lastName = payload.data.lastName;
if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; }
body.phonenumber = payload.data.phonenumber;
body.mobilenumber = payload.data.mobilenumber;
body.gender = payload.data.gender;
console.log('id', payload.data.id, 'and body', body);
patchPerson(payload.data.id, body)
.then(person => new Promise((resolve, reject) => {
console.log('patch person', person);
commit('updatePerson', { target: payload.target, person: person });
resolve();
}));
}
else if (payload.type === 'thirdparty') {
body.name = payload.data.text;
body.email = payload.data.email;
body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id };
console.log('id', payload.data.id, 'and body', body);
patchThirdparty(payload.data.id, body)
.then(thirdparty => new Promise((resolve, reject) => {
console.log('patch thirdparty', thirdparty);
commit('updateThirdparty', { target: payload.target, thirdparty: thirdparty });
resolve();
}));
}
},
/**
* Update accompanying course intensity/emergency/confidentiality
*/
@@ -689,8 +660,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 +681,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 +714,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

@@ -160,7 +160,7 @@
</p>
<ul class="record_actions">
<li>
<li class="add-persons">
<add-persons
ref="handlingThirdPartyPicker"
v-bind:key="handlingThirdPartyPicker.key"
@@ -222,7 +222,7 @@
</div>
<ul class="record_actions">
<li>
<li class="add-persons">
<add-persons
ref="thirdPartyPicker"
v-bind:key="thirdPartyPicker.key"

View File

@@ -397,36 +397,6 @@ const store = createStore({
commit('setErrors', error.violations);
});
},
patchOnTheFly({ commit }, payload) {
let body = { type: payload.type };
const id = payload.data.id;
let url = `/api/1.0/person/person/${id}.json`;
let mutation = "updatePerson";
if (payload.type === 'person') {
body.firstName = payload.data.firstName;
body.lastName = payload.data.lastName;
if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; }
body.phonenumber = payload.data.phonenumber;
body.mobilenumber = payload.data.mobilenumber;
body.gender = payload.data.gender;
} else if (payload.type === 'thirdparty') {
body.name = payload.data.text;
body.email = payload.data.email;
body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id };
url = `/api/1.0/thirdparty/thirdparty/${id}.json`;
mutation = 'updateThirdparty'
}
makeFetch('PATCH', url, body)
.then((response) => {
commit(mutation, {target: payload.target, thirdparty: response});
})
.catch((error) => {
throw error;
})
},
}
});

View File

@@ -29,7 +29,7 @@
</div>
<ul class="record_actions">
<li>
<li class="add-persons">
<add-persons
buttonTitle="household_members_editor.concerned.add_persons"
modalTitle="household_members_editor.concerned.search"

View File

@@ -61,7 +61,7 @@
<li v-if="hasHousehold">
<button @click="resetMode" class="btn btn-sm btn-misc">{{ $t('household_members_editor.household.reset_mode')}}</button>
</li>
<li v-if="!hasHousehold">
<li v-if="!hasHousehold" class="add-persons">
<add-persons
modalTitle="Chercher un ménage existant"
buttonTitle="Chercher un ménage existant"

View File

@@ -1,10 +1,7 @@
<template>
<ul class="record_actions">
<li class="add-persons">
<a class="btn" :class="getClassButton" :title="$t(buttonTitle)"
@click="openModal"><span v-if="displayTextButton">{{ $t(buttonTitle) }}</span></a>
</li>
</ul>
<a class="btn" :class="getClassButton" :title="$t(buttonTitle)" @click="openModal">
<span v-if="displayTextButton">{{ $t(buttonTitle) }}</span>
</a>
<teleport to="body">
<modal v-if="modal.showModal"
@@ -66,8 +63,10 @@
<div class="create-button">
<on-the-fly
v-if="query.length >= 3"
v-if="queryLength >= 3 && (options.type.includes('person') || options.type.includes('thirdparty'))"
:buttonText="$t('onthefly.create.button', {q: query})"
:allowedTypes="options.type"
:query="query"
action="create"
@saveFormOnTheFly="saveFormOnTheFly"
ref="onTheFly">

View File

@@ -32,6 +32,14 @@
<label for="lastname">{{ $t('person.lastname') }}</label>
</div>
<div v-if="queryItems">
<ul class="list-suggest add-items inline">
<li v-for="(qi, i) in queryItems" :key="i" @click="addQueryItem('lastName', qi)">
<span class="person-text">{{ qi }}</span>
</li>
</ul>
</div>
<div class="form-floating mb-3">
<input
class="form-control form-control-lg"
@@ -43,6 +51,14 @@
<label for="firstname">{{ $t('person.firstname') }}</label>
</div>
<div v-if="queryItems">
<ul class="list-suggest add-items inline">
<li v-for="(qi, i) in queryItems" :key="i" @click="addQueryItem('firstName', qi)">
<span class="person-text">{{ qi }}</span>
</li>
</ul>
</div>
<div v-for="(a, i) in config.altNames" :key="a.key" class="form-floating mb-3">
<input
class="form-control form-control-lg"
@@ -122,7 +138,7 @@ import PersonRenderBox from '../Entity/PersonRenderBox.vue';
export default {
name: "OnTheFlyPerson",
props: ['id', 'type', 'action'],
props: ['id', 'type', 'action', 'query'],
//emits: ['createAction'],
components: {
PersonRenderBox
@@ -203,6 +219,9 @@ export default {
},
personAltNamesLabels() {
return this.person.altNames.map(a => a ? a.label : '');
},
queryItems() {
return this.query ? this.query.split(' ') : null;
}
},
mounted() {
@@ -244,6 +263,16 @@ export default {
)
this.person.altNames = updateAltNames;
},
addQueryItem(field, queryItem) {
switch (field) {
case 'lastName':
this.person.lastName = queryItem;
break;
case 'firstName':
this.person.firstName = queryItem;
break;
}
}
}
}
</script>

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

@@ -26,9 +26,9 @@
</div>
<div class="wh-row">
<div class="wh-col">
{% if period.closingDate == null %}
{% if period.closingDate == null and period.step != 'DRAFT' %}
{{ 'accompanying_period.dates_from_%opening_date%'|trans({ '%opening_date%': period.openingDate|format_date('long') } ) }}
{% else %}
{% elseif period.closingDate != null %}
{{ 'accompanying_period.dates_from_%opening_date%_to_%closing_date%'|trans({
'%opening_date%': period.openingDate|format_date('long'),
'%closing_date%': period.closingDate|format_date('long')}
@@ -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 %}
@@ -55,65 +55,67 @@
</div>
</div>
</div>
<div class="item-row separator">
<div class="wrap-list">
{% if period.requestorPerson is not null or period.requestorThirdParty is not null %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Requestor'|trans({'gender': null }) }}</h3></div>
<div class="wl-col list">
{% if period.requestorPerson is not null %}
<span class="wl-item">
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: period.requestorPerson.id },
buttonText: period.requestorPerson|chill_entity_render_string,
isDead: period.requestorPerson.deathdate is not null
} %}
</span>
{% endif %}
{% if period.requestorThirdParty is not null %}
<span class="wl-item">
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'thirdparty', id: period.requestorThirdParty.id },
buttonText: period.requestorThirdParty|chill_entity_render_string
} %}
</span>
{% endif %}
{% if period.requestor is not null or period.participations.count > 0 or period.socialIssues.count > 0%}
<div class="item-row separator">
<div class="wrap-list">
{% if period.requestorPerson is not null or period.requestorThirdParty is not null %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Requestor'|trans({'gender': null }) }}</h3></div>
<div class="wl-col list">
{% if period.requestorPerson is not null %}
<span class="wl-item">
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: period.requestorPerson.id },
buttonText: period.requestorPerson|chill_entity_render_string,
isDead: period.requestorPerson.deathdate is not null
} %}
</span>
{% endif %}
{% if period.requestorThirdParty is not null %}
<span class="wl-item">
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'thirdparty', id: period.requestorThirdParty.id },
buttonText: period.requestorThirdParty|chill_entity_render_string
} %}
</span>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% if period.participations.count > 0 %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Participants'|trans }}</h3></div>
<div class="wl-col list">
{% for p in period.getCurrentParticipations %}
<span class="wl-item">
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: p.person.id },
buttonText: p.person|chill_entity_render_string,
isDead: p.person.deathdate is not null
} %}
</span>
{% endfor %}
{% endif %}
{% if period.participations.count > 0 %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Participants'|trans }}</h3></div>
<div class="wl-col list">
{% for p in period.getCurrentParticipations %}
<span class="wl-item">
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: p.person.id },
buttonText: p.person|chill_entity_render_string,
isDead: p.person.deathdate is not null
} %}
</span>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if period.socialIssues.count > 0 %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Social issues'|trans }}</h3></div>
<div class="wl-col list">
{% for si in period.socialIssues %}
<p class="wl-item">
{{ si|chill_entity_render_box }}
</p>
{% endfor %}
{% endif %}
{% if period.socialIssues.count > 0 %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Social issues'|trans }}</h3></div>
<div class="wl-col list">
{% for si in period.socialIssues %}
<p class="wl-item">
{{ si|chill_entity_render_box }}
</p>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endif %}
<div class="item-row separator">
<div class="item-col item-meta">
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %}

View File

@@ -0,0 +1,32 @@
{% extends "@ChillMain/layout.html.twig" %}
{% set activeRouteKey = 'chill_person_accompanying_period_user_list' %}
{% block title %}{{ 'My accompanying periods in draft'|trans }}{% endblock title %}
{% macro recordAction(period) %}
<li>
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': period.id }) }}"
class="btn btn-show" title="{{ 'See accompanying period'|trans }}"></a>
</li>
{% endmacro %}
{% block content %}
<div class="col-md-10">
<h1>{{ 'My accompanying periods in draft'|trans }}</h1>
<div class="flex-table accompanyingcourse-list">
{% for period in accompanyingPeriods %}
{% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period, 'recordAction': _self.recordAction(period)} %}
{% else %}
<p class="chill-no-data-statement">{{ 'Any accompanying period'|trans }}</p>
{% endfor %}
</div>
{{ chill_pagination(pagination) }}
</div>
{% endblock %}

View File

@@ -115,6 +115,9 @@
<li>
{{ form_widget(form.editPerson, { 'attr': { 'class': 'dropdown-item' }}) }}
</li>
<li>
{{ form_widget(form.createHousehold, { 'attr': { 'class': 'dropdown-item' }}) }}
</li>
<li>
{{ form_widget(form.createPeriod, { 'attr': { 'class': 'dropdown-item' }}) }}
</li>

View File

@@ -32,14 +32,28 @@
</div>
<div class="item-row separator">
<div class="wrap-list">
{% if p.household is same as p.person.getCurrentHousehold %}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Address'|trans }}</h3>
</div>
<div class="wl-col list">
<p class="item">{{ p.household.getCurrentAddress|chill_entity_render_box }}</p>
</div>
</div>
{% endif %}
<div class="wl-row">
<div class="wl-col title">
<h3>En tant que</h3>
<h3>{{ 'household.As member'|trans }}</h3>
</div>
<div class="wl-col list">
<p class="item">{{ p.position.label|localize_translatable_string }}
<p class="item">
{{ p.position.label|localize_translatable_string }}
{% if p.holder %}
<span class="holder">{{ 'household.holder'|trans }}</span>
<span class="fa-stack fa-holder" title="{{ 'houshold.holder'|trans|e('html_attr') }}">
<i class="fa fa-circle fa-stack-1x text-success"></i>
<i class="fa fa-stack-1x">T</i>
</span>
{% endif %}
</p>
</div>

View File

@@ -29,10 +29,10 @@
<div class="item-col" style="width: 33%;">
<ul class="list-unstyled h3">
{% if a.endDate is not null %}
<li><span class="item-key">{{'Since'|trans}} : </span>{{ a.startDate|format_date('long') }}</li>
{% if a.endDate is not null %}
<li><span class="item-key">{{'Until'|trans}} : </span><b>{{ a.endDate|format_date('long') }}</b></li>
{% endif %}
<li><span class="item-key">{{'Until'|trans}} : </span><b>{{ a.endDate|format_date('long') }}</b></li>
</ul>
</div>
<div class="item-col flex-column justify-content-start">

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,17 +53,40 @@ 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");
case self::CREATE:
case self::UPDATE:
return $this->security->isGranted(AccompanyingPeriodVoter::EDIT, $subject->getAccompanyingPeriod());
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);
case self::CREATE:
return $this->security->isGranted(AccompanyingPeriodVoter::CREATE, $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
{
return [self::SEE];
return [self::SEE, self::CREATE, self::UPDATE];
}
}

View File

@@ -91,7 +91,7 @@ class PersonJsonNormalizer implements
'deathdate',
'center',
'altNames',
'email'
'email',
];
$fields = array_filter(

View File

@@ -241,7 +241,8 @@ class AccompanyingPeriodContext implements
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$doc = new AccompanyingCourseDocument();
$doc->setTitle($this->translatableStringHelper->localize($template->getName()))
$doc->setTemplate($template)
->setTitle($this->translatableStringHelper->localize($template->getName()))
->setDate(new DateTime())
->setDescription($this->translatableStringHelper->localize($template->getName()))
->setCourse($entity)

View File

@@ -21,6 +21,7 @@ use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request;
use function array_map;
@@ -58,10 +59,16 @@ final class HouseholdApiControllerTest extends WebTestCase
$centerA = $em->getRepository(Center::class)->findOneBy(['name' => 'Center A']);
$nbReference = $em->createQueryBuilder()->select('count(ar)')->from(AddressReference::class, 'ar')
->getQuery()->getSingleScalarResult();
if (0 === $nbReference) {
throw new RuntimeException('any reference found. Add a reference in database to perform this test');
}
$reference = $em->createQueryBuilder()->select('ar')->from(AddressReference::class, 'ar')
->setFirstResult(random_int(0, $nbReference))
->setMaxResults(1)
->getQuery()->getSingleResult();
$p = new Person();
$p->setFirstname('test')->setLastName('test lastname')
->setGender(Person::BOTH_GENDER)
@@ -79,6 +86,7 @@ final class HouseholdApiControllerTest extends WebTestCase
[HouseholdMember::class, $m->getId()],
[User::class, $p->getId()],
[Household::class, $h->getId()],
[Person::class, $p->getId()],
];
yield [$reference->getId(), $h->getId()];

View File

@@ -129,20 +129,27 @@ final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase
$this->assertNull($period->getPinnedComment());
$period->setPinnedComment($comment);
$this->assertSame($period->getPinnedComment(), $comment);
$this->assertSame($period, $comment->getAccompanyingPeriod());
$this->assertEquals(0, count($period->getComments()), 'The initial comment should not appears in the list of comments');
$this->assertNull($comment->getAccompanyingPeriod());
$this->assertEquals(0, count($period->getComments()));
$period->setPinnedComment($replacingComment);
$this->assertSame($period->getPinnedComment(), $replacingComment);
$this->assertSame($period, $replacingComment->getAccompanyingPeriod());
$this->assertEquals(0, count($period->getComments()), 'The initial comment should not appears in the list of comments');
$this->assertNull($comment->getAccompanyingPeriod());
$this->assertNull($replacingComment->getAccompanyingPeriod());
$this->assertSame($period, $comment->getAccompanyingPeriod());
$this->assertEquals(1, count($period->getComments()));
$this->assertContains($comment, $period->getComments());
$period->setPinnedComment(null);
$this->assertNull($period->getPinnedComment());
$this->assertNull($replacingComment->getAccompanyingPeriod());
$this->assertEquals(0, count($period->getComments()), 'The initial comment should not appears in the list of comments');
$this->assertSame($period, $comment->getAccompanyingPeriod());
$this->assertSame($period, $replacingComment->getAccompanyingPeriod());
$this->assertEquals(2, count($period->getComments()));
$this->assertContains($comment, $period->getComments());
$this->assertContains($replacingComment, $period->getComments());
}
public function testRequestor()

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

@@ -98,6 +98,7 @@ household:
from: Depuis
to: Jusqu'au
person history: Ménages
As member: En tant que
household_composition:
Since: >-

View File

@@ -124,7 +124,7 @@ address_country_code: Code pays
'Alreay existing person': 'Dossiers déjà encodés'
'Add the person': 'Ajouter la personne'
'Add the person and create an accompanying period': "Créer la personne & créer une période d'accompagnement"
'Add the person and create an household': "Créer la personne & créer un ménage"
'Add the person and create a household': "Créer la personne & créer un ménage"
Show person: Voir le dossier de la personne
'Confirm the creation': 'Confirmer la création'
'You will create this person': 'Vous allez créer le dossier suivant'
@@ -179,6 +179,7 @@ An accompanying period is open: Une période d'accompagnement est ouverte
Accompanying period list: Périodes d'accompagnement
Accompanying period list for person: Périodes d'accompagnement de la personne
Accompanying period: Parcours d'accompagnement
Any accompanying period: Aucun parcours d'accompagnement
period: Parcours
New accompanying course: Nouveau parcours d'accompagnement
Choose a motive: Motif de fermeture
@@ -211,7 +212,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
@@ -557,7 +558,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
@@ -566,3 +567,4 @@ Linked evaluations: Évaluations associées
# Accompanying period per user
My accompanying periods: Mes parcours
My accompanying periods in draft: Mes parcours brouillons