Merge branch 'master' into issue321_layout_improvements_actionForm

This commit is contained in:
Mathieu Jaumotte 2021-12-20 12:56:41 +01:00
commit ae43e23ec6
16 changed files with 231 additions and 77 deletions

View File

@ -12,6 +12,16 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* AddAddress: optimize loading: wait for the user finish typing;
* UserPicker: fix bug with deprecated role
* docgen: add base context + tests
* docgen: add age for person
* [household menu] fix filiation order https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/265
## Test releases
### test release 2021-12-14
* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262) * [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262)
* [vendee/person] fix typo "situation professionelle" => "situation professionnelle" * [vendee/person] fix typo "situation professionelle" => "situation professionnelle"
* [main] add availableForUsers condition from locationType in the location API endpoint (champs-libres/departement-de-la-vendee/accent-suivi-developpement#248) * [main] add availableForUsers condition from locationType in the location API endpoint (champs-libres/departement-de-la-vendee/accent-suivi-developpement#248)
@ -24,8 +34,6 @@ and this project adheres to
* [acompanyingCourse] add initial comment on Resume page * [acompanyingCourse] add initial comment on Resume page
* [person] create button full width (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/330) * [person] create button full width (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/330)
## Test releases
### test release 2021-12-11 ### test release 2021-12-11
* [main] add order field to civility * [main] add order field to civility

View File

@ -0,0 +1,53 @@
<?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\DocGeneratorBundle\Service\Context;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use DateTimeImmutable;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class BaseContextData
{
private NormalizerInterface $normalizer;
private Security $security;
public function __construct(Security $security, NormalizerInterface $normalizer)
{
$this->security = $security;
$this->normalizer = $normalizer;
}
public function getData(): array
{
$data = [];
$user = $this->security->getUser();
$data['creator'] = $this->normalizer->normalize(
$user instanceof User ? $user : null,
'docgen',
['docgen:expects' => User::class, 'groups' => ['docgen:read']]
);
$data['createdAt'] = $this->normalizer->normalize(new DateTimeImmutable(), 'docgen', [
'docgen:expects' => DateTimeImmutable::class, 'groups' => ['docgen:read'],
]);
$data['location'] = $this->normalizer->normalize(
$user instanceof User ? $user->getCurrentLocation() : null,
'docgen',
['docgen:expects' => Location::class, 'groups' => ['docgen:read']]
);
return $data;
}
}

View File

@ -34,6 +34,11 @@ services:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
Chill\DocGeneratorBundle\Service\Context\:
resource: "../Service/Context/"
autowire: true
autoconfigure: true
Chill\DocGeneratorBundle\GeneratorDriver\: Chill\DocGeneratorBundle\GeneratorDriver\:
resource: "../GeneratorDriver/" resource: "../GeneratorDriver/"
autowire: true autowire: true

View File

@ -0,0 +1,68 @@
<?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\DocGeneratorBundle\tests\Service\Context;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\MainBundle\Entity\User;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* @internal
* @coversNothing
*/
final class BaseContextDataTest extends KernelTestCase
{
protected function setUp()
{
parent::setUp();
self::bootKernel();
}
public function testGenerateNoContext()
{
$context = $this->buildBaseContext();
$actual = $context->getData();
$this->assertIsArray($actual);
$this->assertArrayHasKey('creator', $actual);
$this->assertArrayHasKey('createdAt', $actual);
$this->assertArrayHasKey('location', $actual);
}
public function testGenerateWithUser()
{
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn(new User());
$context = $this->buildBaseContext($security->reveal());
$actual = $context->getData();
$this->assertIsArray($actual);
$this->assertArrayHasKey('creator', $actual);
$this->assertArrayHasKey('createdAt', $actual);
$this->assertArrayHasKey('location', $actual);
}
private function buildBaseContext(
?Security $security = null,
?NormalizerInterface $normalizer = null
): BaseContextData {
return new BaseContextData(
$security ?? self::$container->get(Security::class),
$normalizer ?? self::$container->get(NormalizerInterface::class)
);
}
}

View File

@ -34,9 +34,6 @@ var download = (button) => {
key, url key, url
; ;
console.log('keyData', keyData);
console.log('ivData', ivData);
button.textContent = labelPreparing; button.textContent = labelPreparing;
window.fetch(urlGenerator) window.fetch(urlGenerator)
@ -48,36 +45,25 @@ var download = (button) => {
} }
}) })
.then(data => { .then(data => {
url = data.url; return window.fetch(data.url);
if (keyData.length > 0) {
return window.crypto.subtle.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']);
}
return Promise.resolve(undefined);
}) })
.then(nKey => { .then(response => {
key = nKey; if (response.ok) {
return response.arrayBuffer();
return window.fetch(url);
})
.then(r => {
console.log('r', r);
if (r.ok) {
return r.arrayBuffer();
} else {
throw new Error(r.status + r.statusText);
} }
throw new Error(response.status + response.statusText);
}) })
.then(buffer => { .then(buffer => {
console.log('buffer', buffer); if (keyData.alg !== undefined) {
if (keyData.length > 0) { return window.crypto.subtle
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer); .importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt'])
.then(key => {
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer);
});
} }
return Promise.resolve(buffer); return Promise.resolve(buffer);
}) })
.then(decrypted => { .then(decrypted => {
console.log('decrypted', decrypted);
var var
blob = new Blob([decrypted], { type: mimeType }), blob = new Blob([decrypted], { type: mimeType }),
url = window.URL.createObjectURL(blob) url = window.URL.createObjectURL(blob)
@ -96,7 +82,6 @@ var download = (button) => {
button.click(); button.click();
}) })
.catch(error => { .catch(error => {
console.log(error);
button.textContent = ""; button.textContent = "";
button.appendChild(document.createTextNode("error while handling decrypted file")); button.appendChild(document.createTextNode("error while handling decrypted file"));
}) })

View File

@ -25,10 +25,10 @@ class LocationApiController extends ApiController
->leftJoin('e.locationType', 'lt') ->leftJoin('e.locationType', 'lt')
->andWhere( ->andWhere(
$query->expr()->andX( $query->expr()->andX(
$query->expr()->eq('e.availableForUsers', "'TRUE'"), $query->expr()->eq('e.availableForUsers', "'TRUE'"),
$query->expr()->eq('lt.availableForUsers', "'TRUE'"), $query->expr()->eq('lt.availableForUsers', "'TRUE'"),
$query->expr()->eq('e.active', "'TRUE'"), $query->expr()->eq('e.active', "'TRUE'"),
) )
); );
} }
} }

View File

@ -40,7 +40,7 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
/** /**
* @ORM\ManyToOne(targetEntity=Address::class, cascade={"persist"}) * @ORM\ManyToOne(targetEntity=Address::class, cascade={"persist"})
* @ORM\JoinColumn(nullable=true) * @ORM\JoinColumn(nullable=true)
* @Serializer\Groups({"read", "write"}) * @Serializer\Groups({"read", "write", "docgen:read"})
*/ */
private ?Address $address = null; private ?Address $address = null;
@ -72,26 +72,26 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
* @ORM\Id * @ORM\Id
* @ORM\GeneratedValue * @ORM\GeneratedValue
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "docgen:read"})
*/ */
private ?int $id = null; private ?int $id = null;
/** /**
* @ORM\ManyToOne(targetEntity=LocationType::class) * @ORM\ManyToOne(targetEntity=LocationType::class)
* @ORM\JoinColumn(nullable=false) * @ORM\JoinColumn(nullable=false)
* @Serializer\Groups({"read", "write"}) * @Serializer\Groups({"read", "write", "docgen:read"})
*/ */
private ?LocationType $locationType = null; private ?LocationType $locationType = null;
/** /**
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @Serializer\Groups({"read", "write"}) * @Serializer\Groups({"read", "write", "docgen:read"})
*/ */
private ?string $name = null; private ?string $name = null;
/** /**
* @ORM\Column(type="string", length=64, nullable=true) * @ORM\Column(type="string", length=64, nullable=true)
* @Serializer\Groups({"read", "write"}) * @Serializer\Groups({"read", "write", "docgen:read"})
* @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/") * @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/")
* @PhonenumberConstraint(type="any") * @PhonenumberConstraint(type="any")
*/ */
@ -99,7 +99,7 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
/** /**
* @ORM\Column(type="string", length=64, nullable=true) * @ORM\Column(type="string", length=64, nullable=true)
* @Serializer\Groups({"read", "write"}) * @Serializer\Groups({"read", "write", "docgen:read"})
* @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/") * @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/")
* @PhonenumberConstraint(type="any") * @PhonenumberConstraint(type="any")
*/ */

View File

@ -71,13 +71,14 @@ class LocationType
* @ORM\Id * @ORM\Id
* @ORM\GeneratedValue * @ORM\GeneratedValue
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "docgen:read"})
*/ */
private ?int $id = null; private ?int $id = null;
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/ */
private array $title = []; private array $title = [];

View File

@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface; use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\Entity\UserRender;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\Options;
@ -47,16 +48,20 @@ class UserPickerType extends AbstractType
protected UserRepository $userRepository; protected UserRepository $userRepository;
private UserRender $userRender;
public function __construct( public function __construct(
AuthorizationHelper $authorizationHelper, AuthorizationHelper $authorizationHelper,
TokenStorageInterface $tokenStorage, TokenStorageInterface $tokenStorage,
UserRepository $userRepository, UserRepository $userRepository,
UserACLAwareRepositoryInterface $userACLAwareRepository UserACLAwareRepositoryInterface $userACLAwareRepository,
UserRender $userRender
) { ) {
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->userRepository = $userRepository; $this->userRepository = $userRepository;
$this->userACLAwareRepository = $userACLAwareRepository; $this->userACLAwareRepository = $userACLAwareRepository;
$this->userRender = $userRender;
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
@ -74,14 +79,19 @@ class UserPickerType extends AbstractType
->setAllowedTypes('having_permissions_group_flag', ['string', 'null']) ->setAllowedTypes('having_permissions_group_flag', ['string', 'null'])
->setDefault('class', User::class) ->setDefault('class', User::class)
->setDefault('placeholder', 'Choose an user') ->setDefault('placeholder', 'Choose an user')
->setDefault('choice_label', static function (User $u) { ->setDefault('choice_label', function (User $u) {
return $u->getUsername(); return $this->userRender->renderString($u, []);
}) })
->setDefault('scope', null) ->setDefault('scope', null)
->setAllowedTypes('scope', [Scope::class, 'array', 'null']) ->setAllowedTypes('scope', [Scope::class, 'array', 'null'])
->setNormalizer('choices', function (Options $options) { ->setNormalizer('choices', function (Options $options) {
if ($options['role'] instanceof Role) {
$role = $options['role']->getRole();
} else {
$role = $options['role'];
}
$users = $this->userACLAwareRepository $users = $this->userACLAwareRepository
->findUsersByReachedACL($options['role']->getRole(), $options['center'], $options['scope'], true); ->findUsersByReachedACL($role, $options['center'], $options['scope'], true);
if (null !== $options['having_permissions_group_flag']) { if (null !== $options['having_permissions_group_flag']) {
return $this->userRepository return $this->userRepository

View File

@ -57,7 +57,7 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
'routeParameters' => [ 'routeParameters' => [
'household_id' => $household->getId(), 'household_id' => $household->getId(),
], ]) ], ])
->setExtras(['order' => 40]); ->setExtras(['order' => 15]);
} }
public static function getMenuIds(): array public static function getMenuIds(): array

View File

@ -3,44 +3,54 @@
*/ */
const parametersToString = ({ query, options }) => { const parametersToString = ({ query, options }) => {
let types =''; let types ='';
options.type.forEach(function(type) { options.type.forEach(function(type) {
types += '&type[]=' + type; types += '&type[]=' + type;
}); });
return 'q=' + query + types; return 'q=' + query + types;
}; };
/* /*
* Endpoint chill_person_search * Endpoint chill_person_search
* method GET, get a list of persons * method GET, get a list of persons
* *
* @query string - the query to search for * @query string - the query to search for
* @deprecated
*/ */
const searchPersons = ({ query, options }) => { const searchPersons = ({ query, options }, signal) => {
console.err('deprecated');
let queryStr = parametersToString({ query, options }); let queryStr = parametersToString({ query, options });
let url = `/fr/search.json?name=person_regular&${queryStr}`; let url = `/fr/search.json?name=person_regular&${queryStr}`;
return fetch(url) let fetchOpts = {
method: 'GET',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
signal,
};
return fetch(url, fetchOpts)
.then(response => { .then(response => {
if (response.ok) { return response.json(); } if (response.ok) { return response.json(); }
throw Error('Error with request resource response'); throw Error('Error with request resource response');
}); });
}; };
/* /*
* Endpoint v.2 chill_main_search_global * Endpoint v.2 chill_main_search_global
* method GET, get a list of persons and thirdparty * method GET, get a list of persons and thirdparty
* *
* NOTE: this is a temporary WIP endpoint, return inconsistent random results * @param query string - the query to search for
* @query string - the query to search for *
*/ */
const searchPersons_2 = ({ query, options }) => { const searchEntities = ({ query, options }, signal) => {
let queryStr = parametersToString({ query, options }); let queryStr = parametersToString({ query, options });
let url = `/api/1.0/search.json?${queryStr}`; let url = `/api/1.0/search.json?${queryStr}`;
return fetch(url) return fetch(url)
.then(response => { .then(response => {
if (response.ok) { return response.json(); } if (response.ok) { return response.json(); }
throw Error('Error with request resource response'); throw Error('Error with request resource response');
}); });
}; };
export { searchPersons, searchPersons_2 }; export { searchPersons, searchEntities };

View File

@ -90,7 +90,7 @@
import Modal from 'ChillMainAssets/vuejs/_components/Modal'; import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue'; import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
import PersonSuggestion from './AddPersons/PersonSuggestion'; import PersonSuggestion from './AddPersons/PersonSuggestion';
import { searchPersons, searchPersons_2 } from 'ChillPersonAssets/vuejs/_api/AddPersons'; import { searchEntities } from 'ChillPersonAssets/vuejs/_api/AddPersons';
import { postPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly"; import { postPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly";
import { postThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly"; import { postThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly";
@ -115,6 +115,8 @@ export default {
}, },
search: { search: {
query: "", query: "",
previousQuery: "",
currentSearchQueryController: null,
suggested: [], suggested: [],
selected: [], selected: [],
priorSuggestion: {} priorSuggestion: {}
@ -189,16 +191,24 @@ export default {
}, },
setQuery(query) { setQuery(query) {
this.search.query = query; this.search.query = query;
if (query.length >= 3) {
searchPersons_2({ query, options: this.options }) setTimeout(function() {
.then(suggested => new Promise((resolve, reject) => { if (query === "") {
//console.log('suggested', suggested); this.loadSuggestions([]);
this.loadSuggestions(suggested.results); return;
resolve(); }
})); if (query === this.search.query) {
} else { if (this.currentSearchQueryController !== undefined) {
this.loadSuggestions([]); this.currentSearchQueryController.abort()
} }
this.currentSearchQueryController = new AbortController();
searchEntities({ query, options: this.options }, this.currentSearchQueryController)
.then(suggested => new Promise((resolve, reject) => {
this.loadSuggestions(suggested.results);
resolve();
}));
}
}.bind(this), query.length > 3 ? 300 : 700);
}, },
loadSuggestions(suggested) { loadSuggestions(suggested) {
this.search.suggested = suggested; this.search.suggested = suggested;

View File

@ -74,7 +74,7 @@ class PersonDocGenNormalizer implements
$data = [ $data = [
'type' => 'person', 'type' => 'person',
'isNull' => false, 'isNull' => false,
'civility' => $this->normalizer->normalize($person->getCivility(), $format, array_merge($context, ['docgen:expect' => Civility::class])), 'civility' => $this->normalizer->normalize($person->getCivility(), $format, array_merge($context, ['docgen:expects' => Civility::class])),
'firstname' => $person->getFirstName(), 'firstname' => $person->getFirstName(),
'lastname' => $person->getLastName(), 'lastname' => $person->getLastName(),
'altNames' => implode( 'altNames' => implode(
@ -87,6 +87,7 @@ class PersonDocGenNormalizer implements
) )
), ),
'text' => $this->personRender->renderString($person, []), 'text' => $this->personRender->renderString($person, []),
'age' => (int) $person->getAge(),
'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $dateContext), 'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $dateContext),
'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $dateContext), 'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $dateContext),
'gender' => $this->translator->trans($person->getGender()), 'gender' => $this->translator->trans($person->getGender()),

View File

@ -15,6 +15,7 @@ use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface; use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException; use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository; use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
@ -38,6 +39,8 @@ class AccompanyingPeriodContext implements
DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithAdminFormInterface,
DocGeneratorContextWithPublicFormInterface DocGeneratorContextWithPublicFormInterface
{ {
private BaseContextData $baseContextData;
private DocumentCategoryRepository $documentCategoryRepository; private DocumentCategoryRepository $documentCategoryRepository;
private EntityManagerInterface $em; private EntityManagerInterface $em;
@ -56,7 +59,8 @@ class AccompanyingPeriodContext implements
TranslatableStringHelperInterface $translatableStringHelper, TranslatableStringHelperInterface $translatableStringHelper,
EntityManagerInterface $em, EntityManagerInterface $em,
PersonRender $personRender, PersonRender $personRender,
TranslatorInterface $translator TranslatorInterface $translator,
BaseContextData $baseContextData
) { ) {
$this->documentCategoryRepository = $documentCategoryRepository; $this->documentCategoryRepository = $documentCategoryRepository;
$this->normalizer = $normalizer; $this->normalizer = $normalizer;
@ -64,12 +68,11 @@ class AccompanyingPeriodContext implements
$this->em = $em; $this->em = $em;
$this->personRender = $personRender; $this->personRender = $personRender;
$this->translator = $translator; $this->translator = $translator;
$this->baseContextData = $baseContextData;
} }
public function adminFormReverseTransform(array $data): array public function adminFormReverseTransform(array $data): array
{ {
dump($data);
if (array_key_exists('category', $data)) { if (array_key_exists('category', $data)) {
$data['category'] = [ $data['category'] = [
'idInsideBundle' => $data['category']->getIdInsideBundle(), 'idInsideBundle' => $data['category']->getIdInsideBundle(),
@ -171,6 +174,7 @@ class AccompanyingPeriodContext implements
$options = $template->getOptions(); $options = $template->getOptions();
$data = []; $data = [];
$data = array_merge($data, $this->baseContextData->getData());
$data['course'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']); $data['course'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']);
foreach (['mainPerson', 'person1', 'person2'] as $k) { foreach (['mainPerson', 'person1', 'person2'] as $k) {

View File

@ -56,6 +56,7 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
'placeOfBirth' => '', 'placeOfBirth' => '',
'memo' => '', 'memo' => '',
'numberOfChildren' => '', 'numberOfChildren' => '',
'age' => '@ignored',
]; ];
private NormalizerInterface $normalizer; private NormalizerInterface $normalizer;

View File

@ -406,10 +406,8 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
/** /**
* Get email. * Get email.
*
* @return string|null
*/ */
public function getEmail() public function getEmail(): ?string
{ {
return $this->email; return $this->email;
} }