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

This commit is contained in:
Julien Fastré 2022-04-29 16:28:29 +02:00
commit cc139782b1
37 changed files with 504 additions and 135 deletions

View File

@ -11,13 +11,20 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [Documents] Validate storedObject and allow for null data (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/565) * Load relationships without gender in french fixtures
* [Activity form] invert 'incoming' and 'receiving' in Activity form * Add command to remove old draft accompanying periods
* [Activity form] keep the same order for 'attendee' field in new and edit form
* [list with period] use "sameas" test operator to introduce requestor in list
## Test releases ## Test releases
### 2021-04-28
* [address] fix bug when editing address: update location and addressreferenceId + better update of the map in edition (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/593)
* [main] avoid address reference search on undefined post code (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/561)
* [person] prevent duplicate relationship in filiation/household graph (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/560)
* [Documents] Validate storedObject and allow for null data (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/565)
* [parcours]: Comments can be unpinned + edit/delete for all users that are allowed to edit parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/566)
### 2021-04-26 ### 2021-04-26
* [Datepickers] datepickers fixed when using keyboard to enter date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/545) * [Datepickers] datepickers fixed when using keyboard to enter date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/545)

View File

@ -5,11 +5,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
-
message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$currentHouseholdParticipationAt\\.$#"
count: 3
path: src/Bundle/ChillPersonBundle/Entity/Person.php
- -
message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$relation\\.$#" message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$relation\\.$#"
count: 1 count: 1

View File

@ -3,6 +3,7 @@ parameters:
paths: paths:
- src/ - src/
excludePaths: excludePaths:
- .php_cs*
- docs/ - docs/
- src/Bundle/*/Tests/* - src/Bundle/*/Tests/*
- src/Bundle/*/tests/* - src/Bundle/*/tests/*

View File

@ -16,9 +16,9 @@ use Chill\MainBundle\Serializer\Model\Collection;
use Exception; use Exception;
use LogicException; use LogicException;
use RuntimeException; use RuntimeException;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
@ -55,7 +55,7 @@ class ApiController extends AbstractCRUDController
return $this->entityDelete('_entity', $request, $id, $_format); return $this->entityDelete('_entity', $request, $id, $_format);
default: default:
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This method is not implemented'); throw new BadRequestHttpException('This method is not implemented');
} }
} }
@ -120,7 +120,7 @@ class ApiController extends AbstractCRUDController
return $this->entityPostAction('_entity', $request, $_format); return $this->entityPostAction('_entity', $request, $_format);
default: default:
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This method is not implemented'); throw new BadRequestHttpException('This method is not implemented');
} }
} }
@ -160,7 +160,7 @@ class ApiController extends AbstractCRUDController
try { try {
$entity = $this->deserialize($action, $request, $_format, $entity); $entity = $this->deserialize($action, $request, $_format, $entity);
} catch (NotEncodableValueException $e) { } catch (NotEncodableValueException $e) {
throw new BadRequestException('invalid json', 400, $e); throw new BadRequestHttpException('invalid json', 400, $e);
} }
$errors = $this->validate($action, $request, $_format, $entity); $errors = $this->validate($action, $request, $_format, $entity);
@ -273,7 +273,7 @@ class ApiController extends AbstractCRUDController
try { try {
$postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext); $postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext);
} catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) { } catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) {
throw new BadRequestException(sprintf('Unable to deserialize posted ' . throw new BadRequestHttpException(sprintf('Unable to deserialize posted ' .
'data: %s', $e->getMessage()), 0, $e); 'data: %s', $e->getMessage()), 0, $e);
} }
@ -290,7 +290,7 @@ class ApiController extends AbstractCRUDController
break; break;
default: default:
throw new BadRequestException('this method is not supported'); throw new BadRequestHttpException('this method is not supported');
} }
$errors = $this->validate($action, $request, $_format, $entity, [$postedData]); $errors = $this->validate($action, $request, $_format, $entity, [$postedData]);
@ -408,7 +408,7 @@ class ApiController extends AbstractCRUDController
return $this->json($entity, Response::HTTP_OK, [], $context); return $this->json($entity, Response::HTTP_OK, [], $context);
} }
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This format is not implemented'); throw new BadRequestHttpException('This format is not implemented');
} }
protected function entityPostAction($action, Request $request, string $_format): Response protected function entityPostAction($action, Request $request, string $_format): Response
@ -418,7 +418,7 @@ class ApiController extends AbstractCRUDController
try { try {
$entity = $this->deserialize($action, $request, $_format, $entity); $entity = $this->deserialize($action, $request, $_format, $entity);
} catch (NotEncodableValueException $e) { } catch (NotEncodableValueException $e) {
throw new BadRequestException('invalid json', 400, $e); throw new BadRequestHttpException('invalid json', 400, $e);
} }
$errors = $this->validate($action, $request, $_format, $entity); $errors = $this->validate($action, $request, $_format, $entity);

View File

@ -25,6 +25,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
@ -1140,6 +1141,6 @@ class CRUDController extends AbstractController
return $this->json($entity, Response::HTTP_OK, [], $context); return $this->json($entity, Response::HTTP_OK, [], $context);
} }
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This format is not implemented'); throw new BadRequestHttpException('This format is not implemented');
} }
} }

View File

@ -22,7 +22,6 @@ use Chill\MainBundle\Search\UnknowSearchNameException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@ -216,7 +215,7 @@ class SearchController extends AbstractController
$types = $request->query->get('type', []); $types = $request->query->get('type', []);
if (count($types) === 0) { if (count($types) === 0) {
throw new BadRequestException('The request must contains at ' throw new BadRequestHttpException('The request must contains at '
. ' one type'); . ' one type');
} }

View File

@ -25,7 +25,6 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@ -144,7 +143,7 @@ class WorkflowController extends AbstractController
public function getAccessByAccessKey(EntityWorkflowStep $entityWorkflowStep, Request $request): Response public function getAccessByAccessKey(EntityWorkflowStep $entityWorkflowStep, Request $request): Response
{ {
if (null === $accessKey = $request->query->get('accessKey', null)) { if (null === $accessKey = $request->query->get('accessKey', null)) {
throw new BadRequestException('accessKey is missing'); throw new BadRequestHttpException('accessKey is missing');
} }
if (!$this->getUser() instanceof User) { if (!$this->getUser() instanceof User) {

View File

@ -628,6 +628,10 @@ export default {
newAddress = Object.assign(newAddress, { newAddress = Object.assign(newAddress, {
'addressReference': this.entity.selected.address.addressReference 'addressReference': this.entity.selected.address.addressReference
}); });
} else {
newAddress = Object.assign(newAddress, {
'addressReference': null
});
} }
if (this.validFrom) { if (this.validFrom) {

View File

@ -8,12 +8,15 @@ import L from 'leaflet';
import markerIconPng from 'leaflet/dist/images/marker-icon.png' import markerIconPng from 'leaflet/dist/images/marker-icon.png'
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
let map;
let marker;
export default { export default {
name: 'AddressMap', name: 'AddressMap',
props: ['entity'], props: ['entity'],
data() {
return {
map: null,
marker: null
}
},
computed: { computed: {
center() { center() {
return this.entity.selected.addressMap.center; return this.entity.selected.addressMap.center;
@ -21,30 +24,33 @@ export default {
}, },
methods:{ methods:{
init() { init() {
map = L.map('address_map').setView([46.67059, -1.42683], 12); this.map = L.map('address_map').setView([46.67059, -1.42683], 12);
map.scrollWheelZoom.disable(); this.map.scrollWheelZoom.disable();
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map); }).addTo(this.map);
const markerIcon = L.icon({ const markerIcon = L.icon({
iconUrl: markerIconPng, iconUrl: markerIconPng,
iconAnchor: [12, 41], iconAnchor: [12, 41],
}); });
marker = L.marker([48.8589, 2.3469], {icon: markerIcon}).addTo(map); this.marker = L.marker([48.8589, 2.3469], {icon: markerIcon});
this.marker.addTo(this.map);
}, },
update() { update() {
//console.log('update map with : ', this.address.addressMap.center) //console.log('update map with : ', this.entity.addressMap.center)
marker.setLatLng(this.entity.addressMap.center); if (this.marker && this.entity.addressMap.center) {
map.setView(this.entity.addressMap.center, 15); this.marker.setLatLng(this.entity.addressMap.center);
this.map.setView(this.entity.addressMap.center, 15);
}
} }
}, },
mounted(){ mounted(){
this.init() this.init();
this.update();
} }
} }
</script> </script>

View File

@ -119,7 +119,7 @@ export default {
}, },
listenInputSearch(query) { listenInputSearch(query) {
//console.log('listenInputSearch', query, this.isAddressSelectorOpen); //console.log('listenInputSearch', query, this.isAddressSelectorOpen);
if (!this.entity.selected.writeNew.postcode) { if (!this.entity.selected.writeNew.postcode && 'id' in this.entity.selected.city) {
if (query.length > 2) { if (query.length > 2) {
this.isLoading = true; this.isLoading = true;
searchReferenceAddresses(query, this.entity.selected.city).then( searchReferenceAddresses(query, this.entity.selected.city).then(

View File

@ -106,6 +106,9 @@ export default {
this.$emit('getReferenceAddresses', this.value); this.$emit('getReferenceAddresses', this.value);
if (this.value.center) { if (this.value.center) {
this.updateMapCenter(this.value.center); this.updateMapCenter(this.value.center);
if (this.value.center.coordinates) {
this.entity.selected.postcode.coordinates = this.value.center.coordinates;
}
} }
} }
}, },

View File

@ -71,6 +71,7 @@ class AddressNormalizer implements ContextAwareNormalizerInterface, NormalizerAw
'id' => $address->getPostCode()->getId(), 'id' => $address->getPostCode()->getId(),
'name' => $address->getPostCode()->getName(), 'name' => $address->getPostCode()->getName(),
'code' => $address->getPostCode()->getCode(), 'code' => $address->getPostCode()->getCode(),
'center' => $address->getPostcode()->getCenter(),
], ],
'country' => [ 'country' => [
'id' => $address->getPostCode()->getCountry()->getId(), 'id' => $address->getPostCode()->getCountry()->getId(),

View File

@ -0,0 +1,64 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Command;
use Chill\PersonBundle\Service\AccompanyingPeriod\OldDraftAccompanyingPeriodRemoverInterface;
use DateInterval;
use Exception;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class RemoveOldDraftAccompanyingPeriodCommand extends Command
{
private LoggerInterface $logger;
private OldDraftAccompanyingPeriodRemoverInterface $remover;
public function __construct(LoggerInterface $logger, OldDraftAccompanyingPeriodRemoverInterface $remover)
{
parent::__construct('chill:person:remove-old-draft-period');
$this->logger = $logger;
$this->remover = $remover;
}
protected function configure(): void
{
$this
->setDescription('Remove draft accompanying period which are still draft and unused')
->addArgument('interval', InputArgument::OPTIONAL, 'The interval for unactive periods', 'P15D');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->logger->info('[' . $this->getName() . '] started', [
'interval' => $input->getArgument('interval'),
]);
try {
$interval = new DateInterval($input->getArgument('interval'));
} catch (Exception $e) {
$this->logger->error('[' . $this->getName() . '] bad interval');
throw $e;
}
$this->remover->remove($interval);
$this->logger->info('[' . $this->getName() . '] end of command');
return 0;
}
}

View File

@ -33,10 +33,10 @@ use DateInterval;
use DateTimeImmutable; use DateTimeImmutable;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Exception\RuntimeException;
@ -185,7 +185,7 @@ final class AccompanyingCourseApiController extends ApiController
->deserialize($request->getContent(), Person::class, $_format, []); ->deserialize($request->getContent(), Person::class, $_format, []);
if (null === $person) { if (null === $person) {
throw new BadRequestException('person id not found'); throw new BadRequestHttpException('person id not found');
} }
// TODO add acl // TODO add acl
@ -204,7 +204,7 @@ final class AccompanyingCourseApiController extends ApiController
break; break;
default: default:
throw new BadRequestException('This method is not supported'); throw new BadRequestHttpException('This method is not supported');
} }
$errors = $this->validator->validate($accompanyingPeriod); $errors = $this->validator->validate($accompanyingPeriod);
@ -247,12 +247,12 @@ final class AccompanyingCourseApiController extends ApiController
} }
if (null === $requestor) { if (null === $requestor) {
throw new BadRequestException('Could not find any person or thirdparty', 0, null); throw new BadRequestHttpException('Could not find any person or thirdparty', 0, null);
} }
$accompanyingPeriod->setRequestor($requestor); $accompanyingPeriod->setRequestor($requestor);
} else { } else {
throw new BadRequestException('method not supported'); throw new BadRequestHttpException('method not supported');
} }
$errors = $this->validator->validate($accompanyingPeriod); $errors = $this->validator->validate($accompanyingPeriod);

View File

@ -186,6 +186,24 @@ class AccompanyingCourseCommentController extends AbstractController
]); ]);
} }
/**
* @Route("/{_locale}/parcours/comment/{id}/unpin", name="chill_person_accompanying_period_comment_unpin")
*/
public function unpinComment(AccompanyingPeriod\Comment $comment): Response
{
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::EDIT, $comment->getAccompanyingPeriod());
$comment->getAccompanyingPeriod()->setPinnedComment(null);
$this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->translator->trans('accompanying_course.comment is unpinned'));
return $this->redirectToRoute('chill_person_accompanying_period_comment_list', [
'accompanying_period_id' => $comment->getAccompanyingPeriod()->getId(),
]);
}
private function createCommentForm(AccompanyingPeriod\Comment $comment, string $step): FormInterface private function createCommentForm(AccompanyingPeriod\Comment $comment, string $step): FormInterface
{ {
return $this->formFactory->createNamed($step, AccompanyingCourseCommentType::class, $comment); return $this->formFactory->createNamed($step, AccompanyingCourseCommentType::class, $comment);

View File

@ -21,9 +21,9 @@ use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\ConstraintViolationInterface;
@ -255,7 +255,7 @@ class AccompanyingCourseController extends Controller
$personIds = $request->query->get('person_id'); $personIds = $request->query->get('person_id');
if (false === is_array($personIds)) { if (false === is_array($personIds)) {
throw new BadRequestException('person_id parameter should be an array'); throw new BadRequestHttpException('person_id parameter should be an array');
} }
foreach ($personIds as $personId) { foreach ($personIds as $personId) {

View File

@ -21,8 +21,8 @@ use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
@ -202,7 +202,7 @@ class HouseholdController extends AbstractController
$this->denyAccessUnlessGranted(HouseholdVoter::EDIT, $household); $this->denyAccessUnlessGranted(HouseholdVoter::EDIT, $household);
if (!$request->query->has('address_id')) { if (!$request->query->has('address_id')) {
throw new BadRequestException('parameter address_id is missing'); throw new BadRequestHttpException('parameter address_id is missing');
} }
$address_id = $request->query->getInt('address_id'); $address_id = $request->query->getInt('address_id');
@ -218,7 +218,7 @@ class HouseholdController extends AbstractController
} }
if (null === $address) { if (null === $address) {
throw new BadRequestException('The edited address does not belongs to the household'); throw new BadRequestHttpException('The edited address does not belongs to the household');
} }
$form = $this->createForm(AddressDateType::class, $address, []); $form = $this->createForm(AddressDateType::class, $address, []);

View File

@ -20,9 +20,9 @@ use Chill\PersonBundle\Form\HouseholdMemberType;
use Chill\PersonBundle\Household\MembersEditor; use Chill\PersonBundle\Household\MembersEditor;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Serializer\Exception; use Symfony\Component\Serializer\Exception;
@ -105,7 +105,7 @@ class HouseholdMemberController extends ApiController
$ids = $request->query->get('persons', []); $ids = $request->query->get('persons', []);
if (0 === count($ids)) { if (0 === count($ids)) {
throw new BadRequestException('parameters persons in query ' . throw new BadRequestHttpException('parameters persons in query ' .
'is not an array or empty'); 'is not an array or empty');
} }
@ -121,7 +121,8 @@ class HouseholdMemberController extends ApiController
} }
} }
if ($householdId = $request->query->get('household', false)) { if ($request->query->has('household')) {
$householdId = $request->query->get('household', false);
$household = $em->getRepository(Household::class) $household = $em->getRepository(Household::class)
->find($householdId); ->find($householdId);
$allowHouseholdCreate = false; $allowHouseholdCreate = false;
@ -189,7 +190,7 @@ class HouseholdMemberController extends ApiController
['groups' => ['read']] ['groups' => ['read']]
); );
} catch (Exception\InvalidArgumentException|Exception\UnexpectedValueException $e) { } catch (Exception\InvalidArgumentException|Exception\UnexpectedValueException $e) {
throw new BadRequestException("Deserialization error: {$e->getMessage()}", 45896, $e); throw new BadRequestHttpException("Deserialization error: {$e->getMessage()}", 45896, $e);
} }
// TODO ACL // TODO ACL

View File

@ -21,23 +21,13 @@ class LoadRelations extends Fixture implements FixtureGroupInterface
public const RELATION_KEY = 'relations'; public const RELATION_KEY = 'relations';
public const RELATIONS = [ public const RELATIONS = [
['title' => ['fr' => 'Mère'], 'reverseTitle' => ['fr' => 'Fille']], ['title' => ['fr' => 'Parent'], 'reverseTitle' => ['fr' => 'Enfant']],
['title' => ['fr' => 'Mère'], 'reverseTitle' => ['fr' => 'Fils']], ['title' => ['fr' => 'En couple'], 'reverseTitle' => ['fr' => 'En couple']],
['title' => ['fr' => 'Père'], 'reverseTitle' => ['fr' => 'Fille']], ['title' => ['fr' => 'Beau parent'], 'reverseTitle' => ['fr' => 'Belle-fille·beau-fils']],
['title' => ['fr' => 'Père'], 'reverseTitle' => ['fr' => 'Fils']], ['title' => ['fr' => 'Frère·Sœur'], 'reverseTitle' => ['fr' => 'Frère·Sœur']],
['title' => ['fr' => 'Demi-frère·sœur'], 'reverseTitle' => ['fr' => 'Demi-frère·sœur']],
['title' => ['fr' => 'Frère'], 'reverseTitle' => ['fr' => 'Frère']], ['title' => ['fr' => 'Grand-parent'], 'reverseTitle' => ['fr' => 'Petit-enfant']],
['title' => ['fr' => 'Soeur'], 'reverseTitle' => ['fr' => 'Soeur']], ['title' => ['fr' => 'Oncle·Tante'], 'reverseTitle' => ['fr' => 'Neveu·Nièce']],
['title' => ['fr' => 'Frère'], 'reverseTitle' => ['fr' => 'Soeur']],
['title' => ['fr' => 'Demi-frère'], 'reverseTitle' => ['fr' => 'Demi-frère']],
['title' => ['fr' => 'Demi-soeur'], 'reverseTitle' => ['fr' => 'Demi-soeur']],
['title' => ['fr' => 'Demi-frère'], 'reverseTitle' => ['fr' => 'Demi-soeur']],
['title' => ['fr' => 'Oncle'], 'reverseTitle' => ['fr' => 'Neveu']],
['title' => ['fr' => 'Oncle'], 'reverseTitle' => ['fr' => 'Nièce']],
['title' => ['fr' => 'Tante'], 'reverseTitle' => ['fr' => 'Neveu']],
['title' => ['fr' => 'Tante'], 'reverseTitle' => ['fr' => 'Nièce']],
]; ];
public static function getGroups(): array public static function getGroups(): array

View File

@ -1258,10 +1258,6 @@ class AccompanyingPeriod implements
*/ */
public function setPinnedComment(?Comment $comment = null): self public function setPinnedComment(?Comment $comment = null): self
{ {
if (null !== $this->pinnedComment) {
$this->removeComment($this->pinnedComment);
}
if (null !== $this->pinnedComment) { if (null !== $this->pinnedComment) {
$this->addComment($this->pinnedComment); $this->addComment($this->pinnedComment);
} }

View File

@ -240,6 +240,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*/ */
private array $currentHouseholdAt = []; private array $currentHouseholdAt = [];
/**
* Cache for the computation of current household participation.
*/
private array $currentHouseholdParticipationAt = [];
/** /**
* The current person address. * The current person address.
* *

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Validator\Constraints\Relationship\RelationshipNoDuplicate;
use DateTimeImmutable; use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@ -31,6 +32,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* @DiscriminatorMap(typeProperty="type", mapping={ * @DiscriminatorMap(typeProperty="type", mapping={
* "relationship": Relationship::class * "relationship": Relationship::class
* }) * })
* @RelationshipNoDuplicate
*/ */
class Relationship implements TrackCreationInterface, TrackUpdateInterface class Relationship implements TrackCreationInterface, TrackUpdateInterface
{ {

View File

@ -472,15 +472,25 @@ export default {
case 'create': case 'create':
return postRelationship(this.modal.data) return postRelationship(this.modal.data)
.then(relationship => new Promise(resolve => { .then(relationship => new Promise(resolve => {
//console.log('post relationship response', relationship) //console.log('post relationship response', relationship)
this.$store.dispatch('addLinkFromRelationship', relationship) this.$store.dispatch('addLinkFromRelationship', relationship)
this.modal.showModal = false this.modal.showModal = false
this.resetForm() this.resetForm()
this.forceUpdateComponent() this.forceUpdateComponent()
resolve() resolve()
})) }))
.catch() .catch( error => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
console.log(v)
}
} else {
this.$toast.open({message: 'An error occurred'});
}
}
)
case 'edit': case 'edit':
return patchRelationship(this.modal.data) return patchRelationship(this.modal.data)

View File

@ -1,50 +1,5 @@
import { splitId } from './vis-network' import { splitId } from './vis-network';
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.js';
/**
* @function makeFetch
* @param method
* @param url
* @param body
* @returns {Promise<Response>}
*/
const makeFetch = (method, url, body) => {
return fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: (body !== null) ? JSON.stringify(body) : null
})
.then(response => {
if (response.ok) {
return response.json();
}
if (response.status === 422) {
return response.json().then(violations => {
throw ValidationException(violations)
});
}
throw {
msg: 'Error while updating AccompanyingPeriod Course.',
sta: response.status,
txt: response.statusText,
err: new Error(),
body: response.body
};
});
}
/**
* @param violations
* @constructor
*/
const ValidationException = (violations) => {
this.violations = violations
this.name = 'ValidationException'
}
/** /**
* @function getFetch * @function getFetch
@ -136,7 +91,7 @@ const getRelationsList = () => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
const postRelationship = (relationship) => { const postRelationship = (relationship) => {
//console.log(relationship) //console.log(relationship);
return postFetch( return postFetch(
`/api/1.0/relations/relationship.json`, `/api/1.0/relations/relationship.json`,
{ {

View File

@ -3,8 +3,10 @@ import { store } from "./store.js"
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import { visMessages } from './i18n' import { visMessages } from './i18n'
import App from './App.vue' import App from './App.vue'
import VueToast from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
import './vis-network' import './vis-network';
const i18n = _createI18n(visMessages) const i18n = _createI18n(visMessages)
const container = document.getElementById('relationship-graph') const container = document.getElementById('relationship-graph')
@ -25,5 +27,11 @@ const app = createApp({
}) })
.use(store) .use(store)
.use(i18n) .use(i18n)
.use(VueToast, {
position: "bottom-right",
type: "error",
duration: 5000,
dismissible: true
})
.component('app', App) .component('app', App)
.mount('#relationship-graph') .mount('#relationship-graph')

View File

@ -8,6 +8,17 @@
{% macro recordAction(comment, isPinned) %} {% macro recordAction(comment, isPinned) %}
{% if isPinned is defined and isPinned == true %} {% if isPinned is defined and isPinned == true %}
<li>
<form method="post" action="{{ chill_path_forward_return_path('chill_person_accompanying_period_comment_unpin', {'id': comment.id}) }}">
<button class="btn btn-sm btn-misc" type="submit">
<span class="fa-stack">
<i class="fa fa-flag fa-stack-1x"></i>
<i class="fa fa-ban fa-stack-2x text-danger"></i>
</span>
{{ 'Unpin comment'|trans }}
</button>
</form>
</li>
{% else %} {% else %}
<li> <li>
<form method="post" action="{{ chill_path_forward_return_path('chill_person_accompanying_period_comment_pin', {'id': comment.id}) }}"> <form method="post" action="{{ chill_path_forward_return_path('chill_person_accompanying_period_comment_pin', {'id': comment.id}) }}">
@ -17,7 +28,7 @@
</form> </form>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_COMMENT_EDIT', comment) %} {% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE', comment.accompanyingPeriod) %}
<li> <li>
<a class="btn btn-sm btn-edit" title="{{ 'Edit'|trans }}" href="{{ path('chill_person_accompanying_period_comment_list', { <a class="btn btn-sm btn-edit" title="{{ 'Edit'|trans }}" href="{{ path('chill_person_accompanying_period_comment_list', {
'_fragment': 'comment-' ~ comment.id, '_fragment': 'comment-' ~ comment.id,
@ -26,7 +37,7 @@
}) }}"></a> }) }}"></a>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_COMMENT_DELETE', comment) %} {% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE', comment.accompanyingPeriod) %}
<li> <li>
<a class="btn btn-sm btn-delete" title="{{ 'Delete'|trans }}" href="{{ path('chill_person_accompanying_period_comment_delete', {'id': comment.id}) }}"></a> <a class="btn btn-sm btn-delete" title="{{ 'Delete'|trans }}" href="{{ path('chill_person_accompanying_period_comment_delete', {'id': comment.id}) }}"></a>
</li> </li>

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Security\Authorization;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use UnexpectedValueException; use UnexpectedValueException;
class AccompanyingPeriodCommentVoter extends Voter class AccompanyingPeriodCommentVoter extends Voter
@ -22,6 +23,13 @@ class AccompanyingPeriodCommentVoter extends Voter
public const EDIT = 'CHILL_PERSON_ACCOMPANYING_PERIOD_COMMENT_EDIT'; public const EDIT = 'CHILL_PERSON_ACCOMPANYING_PERIOD_COMMENT_EDIT';
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
protected function supports($attribute, $subject) protected function supports($attribute, $subject)
{ {
return $subject instanceof Comment; return $subject instanceof Comment;
@ -32,8 +40,10 @@ class AccompanyingPeriodCommentVoter extends Voter
/** @var Comment $subject */ /** @var Comment $subject */
switch ($attribute) { switch ($attribute) {
case self::EDIT: case self::EDIT:
return $this->security->isGranted(AccompanyingPeriodVoter::EDIT, $subject->getAccompanyingPeriod());
case self::DELETE: case self::DELETE:
return $subject->getCreator() === $token->getUser(); return $this->security->isGranted(AccompanyingPeriodVoter::EDIT, $subject->getAccompanyingPeriod());
default: default:
throw new UnexpectedValueException("This attribute {$attribute} is not supported"); throw new UnexpectedValueException("This attribute {$attribute} is not supported");

View File

@ -0,0 +1,103 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Service\AccompanyingPeriod;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use DateInterval;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
class OldDraftAccompanyingPeriodRemover implements OldDraftAccompanyingPeriodRemoverInterface
{
private EntityManagerInterface $em;
private LoggerInterface $logger;
public function __construct(EntityManagerInterface $em, LoggerInterface $logger)
{
$this->em = $em;
$this->logger = $logger;
}
public function remove(DateInterval $interval): void
{
$this->logger->debug('[' . __CLASS__ . '] start to remove old periods', [
'interval' => $interval->format('%d days'),
]);
$beforeDate = (new DateTimeImmutable('now'))->sub($interval);
$results = $this->em->wrapInTransaction(static function (EntityManagerInterface $em) use ($beforeDate) {
$subDQL = 'SELECT p FROM ' . AccompanyingPeriod::class . ' p WHERE p.createdAt < :beforeDate AND p.step = :draft';
$parameters = [
'beforeDate' => $beforeDate,
'draft' => AccompanyingPeriod::STEP_DRAFT,
];
$resources = $em->createQuery(
'DELETE ' . AccompanyingPeriod\Resource::class . " r WHERE r.accompanyingPeriod IN ({$subDQL})"
)
->setParameters($parameters)
->getSingleScalarResult();
$participations = $em->createQuery(
'DELETE ' . AccompanyingPeriodParticipation::class . " part WHERE part.accompanyingPeriod IN ({$subDQL})"
)
->setParameters($parameters)
->getSingleScalarResult();
$userHistory = $em->createQuery(
'DELETE ' . AccompanyingPeriod\UserHistory::class . " h WHERE h.accompanyingPeriod IN ({$subDQL})"
)
->setParameters($parameters)
->getSingleScalarResult();
$comments = $em->createQuery(
'DELETE ' . AccompanyingPeriod\Comment::class . " c WHERE c.accompanyingPeriod IN ({$subDQL})"
)
->setParameters($parameters)
->getSingleScalarResult();
$documents = $em->createQuery(
'DELETE ' . AccompanyingCourseDocument::class . " d WHERE d.course IN ({$subDQL})"
)
->setParameters($parameters)
->getSingleScalarResult();
$periods = $em
->createQuery(
'DELETE ' . AccompanyingPeriod::class . ' p WHERE p.createdAt < :beforeDate
AND p.step = :draft'
)
->setParameter(':beforeDate', $beforeDate, Types::DATETIME_IMMUTABLE)
->setParameter(':draft', AccompanyingPeriod::STEP_DRAFT)
->getSingleScalarResult();
return [
'comments' => $comments,
'documents' => $documents,
'participations' => $participations,
'periods' => $periods,
'resources' => $resources,
'userHistory' => $userHistory,
];
});
$this->logger->info('[' . __CLASS__ . '] periods removed', array_merge($results, [
'interval' => $interval->format('%d days'),
]));
}
}

View File

@ -0,0 +1,19 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Service\AccompanyingPeriod;
use DateInterval;
interface OldDraftAccompanyingPeriodRemoverInterface
{
public function remove(DateInterval $interval): void;
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Validator\Constraints\Relationship;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class RelationshipNoDuplicate extends Constraint
{
public $message = 'relationship.duplicate';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Validator\Constraints\Relationship;
use Chill\PersonBundle\Repository\Relationships\RelationshipRepository;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RelationshipNoDuplicateValidator extends ConstraintValidator
{
private RelationshipRepository $relationshipRepository;
public function __construct(RelationshipRepository $relationshipRepository)
{
$this->relationshipRepository = $relationshipRepository;
}
public function validate($relationship, Constraint $constraint)
{
if (!$constraint instanceof RelationshipNoDuplicate) {
throw new UnexpectedTypeException($constraint, RelationshipNoDuplicate::class);
}
$fromPerson = $relationship->getFromPerson();
$toPerson = $relationship->getToPerson();
$relationships = $this->relationshipRepository->findBy([
'fromPerson' => [$fromPerson, $toPerson],
'toPerson' => [$fromPerson, $toPerson],
]);
foreach ($relationships as $r) {
if (
$r->getFromPerson() === $fromPerson
|| $r->getFromPerson() === $toPerson
|| $r->getToPerson() === $fromPerson
|| $r->getToPerson() === $toPerson
) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add constraint with a index on chill_person_relationships.
*/
final class Version20220425000000 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX IDX_RELATIONSHIPS000');
}
public function getDescription(): string
{
return 'Add constraint with a index on chill_person_relationships.';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE UNIQUE INDEX IDX_RELATIONSHIPS000 ON chill_person_relationships (least(fromperson_id, toperson_id), greatest(fromperson_id, toperson_id))');
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220429133023 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE "accompanying_periods_scopes"
DROP CONSTRAINT "fk_87c4eab032a7a428",
ADD CONSTRAINT "fk_87c4eab032a7a428" FOREIGN KEY (accompanying_period_id) REFERENCES chill_person_accompanying_period(id)
');
}
public function getDescription(): string
{
return 'apply CASCADE DELETE on entity related to accompanying periods';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE "accompanying_periods_scopes"
DROP CONSTRAINT "fk_87c4eab032a7a428",
ADD CONSTRAINT "fk_87c4eab032a7a428" FOREIGN KEY (accompanying_period_id) REFERENCES chill_person_accompanying_period(id) ON DELETE CASCADE
');
}
}

View File

@ -465,6 +465,7 @@ fix it: Compléter
accompanying_course: accompanying_course:
administrative_location: Localisation administrative administrative_location: Localisation administrative
comment is pinned: Le commentaire est épinglé comment is pinned: Le commentaire est épinglé
comment is unpinned: Le commentaire est désépinglé
show: Montrer show: Montrer
hide: Masquer hide: Masquer
closed periods: parcours clôturer closed periods: parcours clôturer
@ -474,6 +475,7 @@ Accompanying Course Comment: Commentaire
Accompanying Course Comment list: Commentaires du parcours Accompanying Course Comment list: Commentaires du parcours
pinned: épinglé pinned: épinglé
Pin comment: Épingler Pin comment: Épingler
Unpin comment: Désépingler
Post a new comment: Poster un nouveau commentaire Post a new comment: Poster un nouveau commentaire
Write a new comment: Écrire un nouveau commentaire Write a new comment: Écrire un nouveau commentaire
Edit a comment: Modifier le commentaire Edit a comment: Modifier le commentaire

View File

@ -62,3 +62,7 @@ You cannot associate a resource with the same person: Vous ne pouvez pas ajouter
#location #location
The period must remain located: 'Un parcours doit être localisé' The period must remain located: 'Un parcours doit être localisé'
The person where the course is located must be associated to the course. Change course's location before removing the person.: "Le parcours est localisé auprès cet usager. Veuillez changer la localisation du parcours avant de suprimer l'usager" The person where the course is located must be associated to the course. Change course's location before removing the person.: "Le parcours est localisé auprès cet usager. Veuillez changer la localisation du parcours avant de suprimer l'usager"
#relationship
relationship:
duplicate: Une relation de filiation existe déjà entre ces 2 personnes

View File

@ -334,7 +334,7 @@ class ReportController extends AbstractController
$cFGroupId = $request->query->get('cFGroup'); $cFGroupId = $request->query->get('cFGroup');
if ($cFGroupId) { if ($request->query->has('cFGroup')) {
return $this->redirect( return $this->redirect(
$this->generateUrl( $this->generateUrl(
'report_new', 'report_new',
@ -391,7 +391,7 @@ class ReportController extends AbstractController
{ {
$cFGroupId = $request->query->get('cFGroup'); $cFGroupId = $request->query->get('cFGroup');
if ($cFGroupId) { if ($request->query->has('cFGroup')) {
return $this->redirect( return $this->redirect(
$this->generateUrl( $this->generateUrl(
'report_export_list', 'report_export_list',

View File

@ -33,7 +33,6 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -494,7 +493,7 @@ final class SingleTaskController extends AbstractController
); );
default: default:
throw new BadRequestException("format not supported: {$_format}"); throw new BadRequestHttpException("format not supported: {$_format}");
} }
} }