Merge branch 'master' into 59_parcours_resu_2

This commit is contained in:
2021-05-25 21:51:06 +02:00
18 changed files with 800 additions and 428 deletions

View File

@@ -1,30 +0,0 @@
<?php
/*
* Copyright (C) 2015-2021 Champs-Libres Coopérative <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\PersonBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
class ApiPersonController extends Controller
{
public function viewAction($id, $_format)
{
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* Copyright (C) 2015-2021 Champs-Libres Coopérative <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Security\Core\Role\Role;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
class PersonApiController extends ApiController
{
private AuthorizationHelper $authorizationHelper;
/**
* @param AuthorizationHelper $authorizationHelper
*/
public function __construct(AuthorizationHelper $authorizationHelper)
{
$this->authorizationHelper = $authorizationHelper;
}
protected function createEntity(string $action, Request $request): object
{
$person = parent::createEntity($action, $request);
// TODO temporary hack to allow creation of person with fake center
$centers = $this->authorizationHelper->getReachableCenters($this->getUser(),
new Role(PersonVoter::CREATE));
$person->setCenter($centers[0]);
return $person;
}
}

View File

@@ -37,32 +37,32 @@ class LoadSocialActions extends AbstractFixture implements OrderedFixtureInterfa
return 10020;
}
public static $socialActions = array(
'social_action_info_conseil' => array(
'title' => array(
public static $socialActions = [
'social_action_info_conseil' => [
'title' => [
'fr' => 'Informer, conseiller'
),
],
'issue' => 'social_issue_prev_prot'
),
'social_action_instruire' => array(
'title' => array(
],
'social_action_instruire' => [
'title' => [
'fr' => 'Instruire l\'imprime unique pour des impayés'
),
],
'issue' => 'social_issue_prev_prot'
),
'social_action_MASP' => array(
'title' => array(
],
'social_action_MASP' => [
'title' => [
'fr' => 'MASP'
),
],
'issue' => 'social_issue_diff_fin'
),
'social_action_protection_enfant' => array(
'title' => array(
],
'social_action_protection_enfant' => [
'title' => [
'fr' => 'Protection Enfant confié dans le cadre judiciaire'
),
],
'issue' => 'social_issue_enfant_protection'
),
);
],
];
public function load(ObjectManager $manager)
{

View File

@@ -38,20 +38,20 @@ class LoadSocialGoals extends AbstractFixture implements OrderedFixtureInterface
return 10030;
}
public static $socialGoals = array(
'social_goal_instuire_dossier' => array(
'title' => array(
public static $socialGoals = [
'social_goal_instuire_dossier' => [
'title' => [
'fr' => 'Instruire le dossier de surendettement'
),
],
'action' => 'social_action_MASP'
),
'social_goal_proteger' => array(
'title' => array(
],
'social_goal_proteger' => [
'title' => [
'fr' => 'Protéger via une assistance educative placement'
),
],
'action' => 'social_action_protection_enfant'
),
);
],
];
public function load(ObjectManager $manager)
{

View File

@@ -37,36 +37,36 @@ class LoadSocialIssues extends AbstractFixture implements OrderedFixtureInterfac
return 10010;
}
public static $socialIssues = array(
'social_issue_diff_fin_or_admin' => array(
'title' => array(
public static $socialIssues = [
'social_issue_diff_fin_or_admin' => [
'title' => [
'fr' => 'ADULTE - DIFFICULTES FINANCIERES ET/OU ADMINISTRATIVES'
)
),
'social_issue_prev_prot' => array(
'title' => array(
]
],
'social_issue_prev_prot' => [
'title' => [
'fr' => 'ADULTE PREVENTION/PROTECTION'
),
],
'parent' => 'social_issue_diff_fin_or_admin'
),
'social_issue_diff_fin' => array(
'title' => array(
],
'social_issue_diff_fin' => [
'title' => [
'fr' => 'Difficulté financière'
),
],
'parent' => 'social_issue_diff_fin_or_admin'
),
'social_issue_enfant_famille' => array(
'title' => array(
],
'social_issue_enfant_famille' => [
'title' => [
'fr' => 'Enfant / famille'
)
),
'social_issue_enfant_protection' => array(
'title' => array(
]
],
'social_issue_enfant_protection' => [
'title' => [
'fr' => 'enfant - protection'
),
],
'parent' => 'social_issue_enfant_famille'
),
);
],
];
public function load(ObjectManager $manager)
{

View File

@@ -38,34 +38,34 @@ class LoadSocialResults extends AbstractFixture implements OrderedFixtureInterfa
return 10040;
}
public static $socialResults = array(
'social_result_FSL_acces' => array(
'title' => array(
public static $socialResults = [
'social_result_FSL_acces' => [
'title' => [
'fr' => 'FSL - accès cautionnement'
),
],
'action' => 'social_action_instruire'
),
'social_result_FSL_maintien' => array(
'title' => array(
],
'social_result_FSL_maintien' => [
'title' => [
'fr' => 'FSL maintien - impayés de loyer'
),
],
'action' => 'social_action_MASP'
),
'social_result_soutien_parental' => array(
'title' => array(
],
'social_result_soutien_parental' => [
'title' => [
'fr' => 'Soutien parental'
),
],
// 'action' => 'social_action_protection_enfant', (via le goal)
'goal' => 'social_goal_proteger'
),
'social_result_accompagnement_mineur' => array(
'title' => array(
],
'social_result_accompagnement_mineur' => [
'title' => [
'fr' => 'Accompagnement du mineur'
),
],
// 'action' => 'social_action_protection_enfant', (via le goal)
'goal' => 'social_goal_proteger',
),
);
],
];
public function load(ObjectManager $manager)
{

View File

@@ -476,7 +476,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
'class' => \Chill\PersonBundle\Entity\SocialWork\SocialIssue::class,
'name' => 'social_work_social_issue',
'base_path' => '/api/1.0/person/social-work/social-issue',
// 'controller' => \Chill\PersonBundle\Controller\OpeningApiController::class,
'base_role' => 'ROLE_USER',
'actions' => [
'_index' => [
@@ -493,6 +492,28 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
],
]
],
[
'class' => \Chill\PersonBundle\Entity\Person::class,
'name' => 'person',
'base_path' => '/api/1.0/person/person',
'base_role' => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE,
'controller' => \Chill\PersonBundle\Controller\PersonApiController::class,
'actions' => [
'_entity' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true,
Request::METHOD_POST=> true,
],
'roles' => [
Request::METHOD_GET => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE,
Request::METHOD_HEAD => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE,
Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\PersonVoter::CREATE,
]
],
]
],
]
]);
}

View File

@@ -18,7 +18,11 @@
*/
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Center;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
@@ -27,6 +31,7 @@ use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
/**
* Serialize a Person entity
@@ -34,16 +39,24 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
*/
class PersonNormalizer implements
NormalizerInterface,
NormalizerAwareInterface
NormalizerAwareInterface,
DenormalizerInterface,
DenormalizerAwareInterface
{
protected NormalizerInterface $normalizer;
private ChillEntityRenderExtension $render;
public function __construct(ChillEntityRenderExtension $render)
private PersonRepository $repository;
use NormalizerAwareTrait;
use ObjectToPopulateTrait;
use DenormalizerAwareTrait;
public function __construct(ChillEntityRenderExtension $render, PersonRepository $repository)
{
$this->render = $render;
$this->repository = $repository;
}
public function normalize($person, string $format = null, array $context = array())
@@ -59,7 +72,9 @@ class PersonNormalizer implements
'center' => $this->normalizer->normalize($person->getCenter()),
'phonenumber' => $person->getPhonenumber(),
'mobilenumber' => $person->getMobilenumber(),
'altNames' => $this->normalizeAltNames($person->getAltNames())
'altNames' => $this->normalizeAltNames($person->getAltNames()),
'gender' => $person->getGender(),
'gender_numeric' => $person->getGenderNumeric(),
];
}
@@ -80,9 +95,50 @@ class PersonNormalizer implements
return $data instanceof Person;
}
public function setNormalizer(NormalizerInterface $normalizer)
public function denormalize($data, string $type, string $format = null, array $context = [])
{
$this->normalizer = $normalizer;
$person = $this->extractObjectToPopulate($type, $context);
if (\array_key_exists('id', $data)) {
$person = $this->repository->find($data['id']);
if (null === $person) {
throw new UnexpectedValueException("The person with id \"{$data['id']}\" does ".
"not exists");
}
// currently, not allowed to update a person through api
// if instantiated with id
return $person;
}
if (null === $person) {
$person = new Person();
}
foreach (['firstName', 'lastName', 'phonenumber', 'mobilenumber', 'gender']
as $item) {
if (\array_key_exists($item, $data)) {
$person->{'set'.\ucfirst($item)}($data[$item]);
}
}
foreach ([
'birthdate' => \DateTime::class,
'center' => Center::class
] as $item => $class) {
if (\array_key_exists($item, $data)) {
$object = $this->denormalizer->denormalize($data[$item], $class, $format, $context);
if ($object instanceof $class) {
$person->{'set'.\ucfirst($item)}($object);
}
}
}
return $person;
}
public function supportsDenormalization($data, string $type, string $format = null)
{
return $type === Person::class && ($data['type'] ?? NULL) === 'person';
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Chill\PersonBundle\Tests\Controller;
use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class PersonApiControllerTest extends WebTestCase
{
use PrepareClientTrait;
/**
* @dataProvider dataGetPersonFromCenterB
*/
public function testPersonGetUnauthorized($personId): void
{
$client = $this->getClientAuthenticated();
$client->request(Request::METHOD_GET, "/api/1.0/person/person/{$personId}.json");
$response = $client->getResponse();
$this->assertEquals(403, $response->getStatusCode());
}
/**
* @dataProvider dataGetPersonFromCenterA
*/
public function testPersonGet($personId): void
{
$client = $this->getClientAuthenticated();
$client->request(Request::METHOD_GET, "/api/1.0/person/person/{$personId}.json");
$response = $client->getResponse();
$this->assertResponseIsSuccessful();
$data = \json_decode($client->getResponse()->getContent(), true);
$this->assertArrayHasKey('type', $data);
$this->assertArrayHasKey('id', $data);
$this->assertEquals('person', $data['type']);
$this->assertEquals($personId, $data['id']);
}
public function dataGetPersonFromCenterA(): \Iterator
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$personIds= $em->createQuery("SELECT p.id FROM ".Person::class." p ".
"JOIN p.center c ".
"WHERE c.name = :center")
->setParameter('center', 'Center A')
->setMaxResults(100)
->getScalarResult()
;
\shuffle($personIds);
yield \array_pop($personIds);
yield \array_pop($personIds);
}
public function dataGetPersonFromCenterB(): \Iterator
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$personIds= $em->createQuery("SELECT p.id FROM ".Person::class." p ".
"JOIN p.center c ".
"WHERE c.name = :center")
->setParameter('center', 'Center B')
->setMaxResults(100)
->getScalarResult()
;
\shuffle($personIds);
yield \array_pop($personIds);
yield \array_pop($personIds);
}
}

View File

@@ -41,6 +41,11 @@ components:
properties:
id:
type: integer
readOnly: true
type:
type: string
enum:
- 'person'
firstName:
type: string
lastName:
@@ -48,12 +53,23 @@ components:
text:
type: string
description: a canonical representation for the person name
readOnly: true
birthdate:
$ref: '#/components/schemas/Date'
phonenumber:
type: string
mobilenumber:
type: string
gender:
type: string
enum:
- man
- woman
- both
gender_numeric:
type: integer
description: a numerical representation of gender
readOnly: true
PersonById:
type: object
properties:
@@ -178,6 +194,53 @@ components:
readOnly: true
paths:
/1.0/person/person/{id}.json:
get:
tags:
- person
summary: Get a single person
parameters:
- name: id
in: path
required: true
description: The person's id
schema:
type: integer
format: integer
minimum: 1
responses:
200:
description: "OK"
content:
application/json:
schema:
$ref: "#/components/schemas/Person"
403:
description: "Unauthorized"
/1.0/person/person.json:
post:
tags:
- person
summary: Create a single person
requestBody:
description: "A person"
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Person'
responses:
200:
description: "OK"
content:
application/json:
schema:
$ref: "#/components/schemas/Person"
403:
description: "Unauthorized"
422:
description: "Invalid data: the data is a valid json, could be deserialized, but does not pass validation"
/1.0/person/social-work/social-issue.json:
get:
tags:

View File

@@ -53,3 +53,9 @@ services:
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
$registry: '@Symfony\Component\Workflow\Registry'
tags: ['controller.service_arguments']
Chill\PersonBundle\Controller\PersonApiController:
arguments:
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
tags: ['controller.service_arguments']