mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-20 14:43:49 +00:00
Merge remote-tracking branch 'origin/master' into HEAD
This commit is contained in:
@@ -73,7 +73,7 @@ class AccompanyingCourseController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $period);
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::CREATE, $period);
|
||||
|
||||
$em->persist($period);
|
||||
$em->flush();
|
||||
@@ -92,6 +92,8 @@ class AccompanyingCourseController extends Controller
|
||||
*/
|
||||
public function indexAction(AccompanyingPeriod $accompanyingCourse): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
|
||||
|
||||
// compute some warnings
|
||||
// get persons without household
|
||||
$withoutHousehold = [];
|
||||
@@ -131,6 +133,8 @@ class AccompanyingCourseController extends Controller
|
||||
*/
|
||||
public function editAction(AccompanyingPeriod $accompanyingCourse): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
|
||||
|
||||
return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [
|
||||
'accompanyingCourse' => $accompanyingCourse
|
||||
]);
|
||||
@@ -139,13 +143,15 @@ class AccompanyingCourseController extends Controller
|
||||
/**
|
||||
* History page of Accompanying Course section
|
||||
*
|
||||
* the page show anti chronologic history with all actions, title of page is 'accompanying course details'
|
||||
* the page show anti chronologic history with all actions, title of page is 'Accompanying Course History'
|
||||
*
|
||||
* @Route("/{_locale}/parcours/{accompanying_period_id}/history", name="chill_person_accompanying_course_history")
|
||||
* @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"})
|
||||
*/
|
||||
public function historyAction(AccompanyingPeriod $accompanyingCourse): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
|
||||
|
||||
return $this->render('@ChillPerson/AccompanyingCourse/history.html.twig', [
|
||||
'accompanyingCourse' => $accompanyingCourse
|
||||
]);
|
||||
|
@@ -23,7 +23,10 @@
|
||||
namespace Chill\PersonBundle\Controller;
|
||||
|
||||
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
||||
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Form\AccompanyingPeriodType;
|
||||
@@ -53,21 +56,24 @@ class AccompanyingPeriodController extends AbstractController
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* AccompanyingPeriodController constructor.
|
||||
*
|
||||
* @param EventDispatcherInterface $eventDispatcher
|
||||
* @param ValidatorInterface $validator
|
||||
*/
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher, ValidatorInterface $validator)
|
||||
{
|
||||
protected AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository;
|
||||
|
||||
public function __construct(
|
||||
AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
ValidatorInterface $validator
|
||||
) {
|
||||
$this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
public function listAction(int $person_id): Response
|
||||
/**
|
||||
* @ParamConverter("person", options={"id"="person_id"})
|
||||
*/
|
||||
public function listAction(Person $person): Response
|
||||
{
|
||||
$person = $this->_getPerson($person_id);
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $person);
|
||||
|
||||
$event = new PrivacyEvent($person, [
|
||||
'element_class' => AccompanyingPeriod::class,
|
||||
@@ -75,9 +81,10 @@ class AccompanyingPeriodController extends AbstractController
|
||||
]);
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
|
||||
$accompanyingPeriods = $person->getAccompanyingPeriodsOrdered();
|
||||
$accompanyingPeriods = $this->accompanyingPeriodACLAwareRepository
|
||||
->findByPerson($person, AccompanyingPeriodVoter::SEE);
|
||||
|
||||
return $this->render('ChillPersonBundle:AccompanyingPeriod:list.html.twig', [
|
||||
return $this->render('@ChillPerson/AccompanyingPeriod/list.html.twig', [
|
||||
'accompanying_periods' => $accompanyingPeriods,
|
||||
'person' => $person
|
||||
]);
|
||||
|
@@ -230,13 +230,16 @@ final class PersonController extends AbstractController
|
||||
*/
|
||||
public function newAction(Request $request)
|
||||
{
|
||||
$defaultCenter = $this->security
|
||||
->getUser()
|
||||
->getGroupCenters()[0]
|
||||
->getCenter();
|
||||
$person = new Person();
|
||||
|
||||
$person = (new Person(new \DateTime('now')))
|
||||
->setCenter($defaultCenter);
|
||||
if (1 === count($this->security->getUser()
|
||||
->getGroupCenters())) {
|
||||
$person->setCenter(
|
||||
$this->security->getUser()
|
||||
->getGroupCenters()[0]
|
||||
->getCenter()
|
||||
);
|
||||
}
|
||||
|
||||
$form = $this->createForm(CreationPersonType::class, $person, [
|
||||
'validation_groups' => ['create']
|
||||
@@ -244,7 +247,9 @@ 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 an household'
|
||||
]); // TODO createHousehold form action
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
@@ -24,8 +24,12 @@ namespace Chill\PersonBundle\DataFixtures\ORM;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Country;
|
||||
use Chill\MainBundle\Entity\PostalCode;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Repository\CenterRepository;
|
||||
use Chill\MainBundle\Repository\CountryRepository;
|
||||
use Chill\MainBundle\Repository\ScopeRepository;
|
||||
use Chill\MainBundle\Repository\UserRepository;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\MaritalStatus;
|
||||
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
|
||||
@@ -90,12 +94,26 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
||||
|
||||
protected MaritalStatusRepository $maritalStatusRepository;
|
||||
|
||||
/**
|
||||
* @var array|Scope[]
|
||||
*/
|
||||
protected array $cacheScopes = [];
|
||||
|
||||
protected ScopeRepository $scopeRepository;
|
||||
|
||||
/** @var array|User[] */
|
||||
protected array $cacheUsers = [];
|
||||
|
||||
protected UserRepository $userRepository;
|
||||
|
||||
public function __construct(
|
||||
Registry $workflowRegistry,
|
||||
SocialIssueRepository $socialIssueRepository,
|
||||
CenterRepository $centerRepository,
|
||||
CountryRepository $countryRepository,
|
||||
MaritalStatusRepository $maritalStatusRepository
|
||||
MaritalStatusRepository $maritalStatusRepository,
|
||||
ScopeRepository $scopeRepository,
|
||||
UserRepository $userRepository
|
||||
) {
|
||||
$this->faker = Factory::create('fr_FR');
|
||||
$this->faker->addProvider($this);
|
||||
@@ -105,7 +123,8 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
||||
$this->countryRepository = $countryRepository;
|
||||
$this->maritalStatusRepository = $maritalStatusRepository;
|
||||
$this->loader = new NativeLoader($this->faker);
|
||||
|
||||
$this->scopeRepository = $scopeRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
|
||||
public function getOrder()
|
||||
@@ -220,10 +239,16 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
||||
new \DateInterval('P' . \random_int(0, 180) . 'D')
|
||||
)
|
||||
);
|
||||
$accompanyingPeriod->setCreatedBy($this->getRandomUser())
|
||||
->setCreatedAt(new \DateTimeImmutable('now'));
|
||||
$person->addAccompanyingPeriod($accompanyingPeriod);
|
||||
$accompanyingPeriod->addSocialIssue($this->getRandomSocialIssue());
|
||||
|
||||
if (\random_int(0, 10) > 3) {
|
||||
// always add social scope:
|
||||
$accompanyingPeriod->addScope($this->getReference('scope_social'));
|
||||
var_dump(count($accompanyingPeriod->getScopes()));
|
||||
|
||||
$accompanyingPeriod->setAddressLocation($this->createAddress());
|
||||
$manager->persist($accompanyingPeriod->getAddressLocation());
|
||||
$workflow = $this->workflowRegistry->get($accompanyingPeriod);
|
||||
@@ -231,9 +256,19 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
||||
}
|
||||
|
||||
$manager->persist($person);
|
||||
$manager->persist($accompanyingPeriod);
|
||||
echo "add person'".$person->__toString()."'\n";
|
||||
}
|
||||
|
||||
private function getRandomUser(): User
|
||||
{
|
||||
if (0 === count($this->cacheUsers)) {
|
||||
$this->cacheUsers = $this->userRepository->findAll();
|
||||
}
|
||||
|
||||
return $this->cacheUsers[\array_rand($this->cacheUsers)];
|
||||
}
|
||||
|
||||
private function createAddress(): Address
|
||||
{
|
||||
$objectSet = $this->loader->loadData([
|
||||
|
@@ -40,13 +40,13 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface
|
||||
return 9600;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) {
|
||||
$permissionsGroup = $this->getReference($permissionsGroupRef);
|
||||
$scopeSocial = $this->getReference('scope_social');
|
||||
|
||||
|
||||
//create permission group
|
||||
switch ($permissionsGroup->getName()) {
|
||||
case 'social':
|
||||
@@ -55,7 +55,7 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface
|
||||
|
||||
$permissionsGroup->addRoleScope(
|
||||
(new RoleScope())
|
||||
->setRole(AccompanyingPeriodVoter::SEE)
|
||||
->setRole(AccompanyingPeriodVoter::FULL)
|
||||
->setScope($scopeSocial)
|
||||
);
|
||||
|
||||
@@ -87,7 +87,7 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface
|
||||
$manager->persist($roleScopeUpdate);
|
||||
$manager->persist($roleScopeCreate);
|
||||
$manager->persist($roleScopeDuplicate);
|
||||
|
||||
|
||||
break;
|
||||
case 'administrative':
|
||||
printf("Adding CHILL_PERSON_SEE to %s permission group \n", $permissionsGroup->getName());
|
||||
@@ -98,9 +98,9 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface
|
||||
$manager->persist($roleScopeSee);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@
|
||||
|
||||
namespace Chill\PersonBundle\DependencyInjection;
|
||||
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
@@ -60,6 +61,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
||||
$container->setParameter('chill_person.allow_multiple_simultaneous_accompanying_periods',
|
||||
$config['allow_multiple_simultaneous_accompanying_periods']);
|
||||
|
||||
// register all configuration in a unique parameter
|
||||
$container->setParameter('chill_person', $config);
|
||||
|
||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
||||
$loader->load('services.yaml');
|
||||
$loader->load('services/widgets.yaml');
|
||||
@@ -255,14 +259,26 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
||||
*/
|
||||
protected function prependRoleHierarchy(ContainerBuilder $container)
|
||||
{
|
||||
$container->prependExtensionConfig('security', array(
|
||||
'role_hierarchy' => array(
|
||||
'CHILL_PERSON_UPDATE' => array('CHILL_PERSON_SEE'),
|
||||
'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE'),
|
||||
PersonVoter::LISTS => [ ChillExportVoter::EXPORT ],
|
||||
PersonVoter::STATS => [ ChillExportVoter::EXPORT ]
|
||||
)
|
||||
));
|
||||
$container->prependExtensionConfig('security', [
|
||||
'role_hierarchy' => [
|
||||
PersonVoter::UPDATE => [PersonVoter::SEE],
|
||||
PersonVoter::CREATE => [PersonVoter::SEE],
|
||||
PersonVoter::LISTS => [ChillExportVoter::EXPORT],
|
||||
PersonVoter::STATS => [ChillExportVoter::EXPORT],
|
||||
// accompanying period
|
||||
AccompanyingPeriodVoter::SEE_DETAILS => [AccompanyingPeriodVoter::SEE],
|
||||
AccompanyingPeriodVoter::CREATE => [AccompanyingPeriodVoter::SEE_DETAILS],
|
||||
AccompanyingPeriodVoter::DELETE => [AccompanyingPeriodVoter::SEE_DETAILS],
|
||||
AccompanyingPeriodVoter::EDIT => [AccompanyingPeriodVoter::SEE_DETAILS],
|
||||
// give all ACL for FULL
|
||||
AccompanyingPeriodVoter::FULL => [
|
||||
AccompanyingPeriodVoter::SEE_DETAILS,
|
||||
AccompanyingPeriodVoter::CREATE,
|
||||
AccompanyingPeriodVoter::EDIT,
|
||||
AccompanyingPeriodVoter::DELETE
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -43,23 +43,26 @@ class Configuration implements ConfigurationInterface
|
||||
->arrayNode('validation')
|
||||
->canBeDisabled()
|
||||
->children()
|
||||
->booleanNode('center_required')
|
||||
->info('Enable a center for each person entity. If disabled, you must provide your own center provider')
|
||||
->defaultValue(true)
|
||||
->end()
|
||||
->scalarNode('birthdate_not_after')
|
||||
->info($this->validationBirthdateNotAfterInfos)
|
||||
->defaultValue('P1D')
|
||||
->validate()
|
||||
->ifTrue(function($period) {
|
||||
try {
|
||||
$interval = new \DateInterval($period);
|
||||
} catch (\Exception $ex) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
->thenInvalid('Invalid period for birthdate validation : "%s" '
|
||||
. 'The parameter should match duration as defined by ISO8601 : '
|
||||
. 'https://en.wikipedia.org/wiki/ISO_8601#Durations')
|
||||
->info($this->validationBirthdateNotAfterInfos)
|
||||
->defaultValue('P1D')
|
||||
->validate()
|
||||
->ifTrue(function($period) {
|
||||
try {
|
||||
$interval = new \DateInterval($period);
|
||||
} catch (\Exception $ex) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
->thenInvalid('Invalid period for birthdate validation : "%s" '
|
||||
. 'The parameter should match duration as defined by ISO8601 : '
|
||||
. 'https://en.wikipedia.org/wiki/ISO_8601#Durations')
|
||||
->end() // birthdate_not_after, parent = children of validation
|
||||
|
||||
->end() // children for 'validation', parent = validation
|
||||
->end() //validation, parent = children of root
|
||||
->end() // children of root, parent = root
|
||||
|
@@ -24,6 +24,8 @@ namespace Chill\PersonBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Entity\HasCentersInterface;
|
||||
use Chill\MainBundle\Entity\HasScopesInterface;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||
@@ -52,7 +54,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
* "accompanying_period"=AccompanyingPeriod::class
|
||||
* })
|
||||
*/
|
||||
class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
||||
class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface,
|
||||
HasScopesInterface, HasCentersInterface
|
||||
{
|
||||
/**
|
||||
* Mark an accompanying period as "occasional"
|
||||
@@ -809,14 +812,21 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable|Collection
|
||||
*/
|
||||
public function getScopes(): Collection
|
||||
{
|
||||
return $this->scopes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function addScope(Scope $scope): self
|
||||
{
|
||||
$this->scopes[] = $scope;
|
||||
if (!$this->scopes->contains($scope)) {
|
||||
$this->scopes[] = $scope;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -1040,4 +1050,16 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
||||
return 'none';
|
||||
}
|
||||
}
|
||||
|
||||
public function getCenters(): ?iterable
|
||||
{
|
||||
foreach ($this->getPersons() as $person) {
|
||||
if (!in_array($person->getCenter(), $centers ?? [])
|
||||
&& NULL !== $person->getCenter()) {
|
||||
$centers[] = $person->getCenter();
|
||||
}
|
||||
}
|
||||
|
||||
return $centers ?? null;
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,29 @@ use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* This class links a person to the history of his addresses, through
|
||||
* household membership.
|
||||
*
|
||||
* It is optimized on DB side, and compute the start date and end date
|
||||
* of each address by the belonging of household.
|
||||
*
|
||||
* **note**: the start date and end date are the date of belonging to the address,
|
||||
* not the belonging of the household.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* * person A is member of household W from 2021-01-01 to 2021-12-01
|
||||
* * person A is member of household V from 2021-12-01, still present after
|
||||
* * household W lives in address Q from 2020-06-01 to 2021-06-01
|
||||
* * household W lives in address R from 2021-06-01 to 2022-06-01
|
||||
* * household V lives in address T from 2021-12-01 to still living there after
|
||||
*
|
||||
* The person A will have those 3 entities:
|
||||
*
|
||||
* 1. 1st entity: from 2021-01-01 to 2021-06-01, household W, address Q;
|
||||
* 2. 2st entity: from 2021-06-01 to 2021-12-01, household W, address R;
|
||||
* 3. 3st entity: from 2021-12-01 to NULL, household V, address T;
|
||||
*
|
||||
* @ORM\Entity(readOnly=true)
|
||||
* @ORM\Table(name="view_chill_person_household_address")
|
||||
*/
|
||||
@@ -45,11 +68,23 @@ class PersonHouseholdAddress
|
||||
*/
|
||||
private $address;
|
||||
|
||||
/**
|
||||
* The start date of the intersection address/household
|
||||
*
|
||||
* (this is not the startdate of the household, not
|
||||
* the startdate of the address)
|
||||
*/
|
||||
public function getValidFrom(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->validFrom;
|
||||
}
|
||||
|
||||
/**
|
||||
* The end date of the intersection address/household
|
||||
*
|
||||
* (this is not the enddate of the household, not
|
||||
* the enddate of the address)
|
||||
*/
|
||||
public function getValidTo(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->validTo;
|
||||
|
@@ -35,6 +35,7 @@ use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||
use Chill\MainBundle\Entity\HasCenterInterface;
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||
use Chill\PersonBundle\Entity\Person\PersonCurrentAddress;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
@@ -43,6 +44,11 @@ use Doctrine\Common\Collections\Criteria;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Chill\PersonBundle\Validator\Constraints\Person\Birthdate;
|
||||
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||
use Chill\PersonBundle\Validator\Constraints\Person\PersonHasCenter;
|
||||
use Chill\PersonBundle\Validator\Constraints\Household\HouseholdMembershipSequential;
|
||||
|
||||
/**
|
||||
* Person Class
|
||||
@@ -57,6 +63,12 @@ use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
|
||||
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "person"=Person::class
|
||||
* })
|
||||
* @PersonHasCenter(
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
* @HouseholdMembershipSequential(
|
||||
* groups={"household_memberships"}
|
||||
* )
|
||||
*/
|
||||
class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
@@ -75,6 +87,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=255)
|
||||
* @Assert\NotBlank(
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
* @Assert\Length(
|
||||
* max=255,
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $firstName;
|
||||
|
||||
@@ -83,6 +102,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=255)
|
||||
* @Assert\NotBlank(
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
* @Assert\Length(
|
||||
* max=255,
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $lastName;
|
||||
|
||||
@@ -102,6 +128,12 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="date", nullable=true)
|
||||
* @Assert\Date(
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
* @Birthdate(
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $birthdate;
|
||||
|
||||
@@ -110,6 +142,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var \DateTimeImmutable
|
||||
*
|
||||
* @ORM\Column(type="date_immutable", nullable=true)
|
||||
* @Assert\Date(
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private ?\DateTimeImmutable $deathdate;
|
||||
|
||||
@@ -150,6 +185,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=9, nullable=true)
|
||||
* @Assert\NotNull(
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $gender;
|
||||
|
||||
@@ -179,8 +217,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="date", nullable=true)
|
||||
* @Assert\Date(
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $maritalStatusDate;
|
||||
private ?\DateTime $maritalStatusDate;
|
||||
|
||||
/**
|
||||
* Comment on marital status
|
||||
@@ -202,6 +243,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
* @Assert\Email(
|
||||
* checkMX=true,
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $email = '';
|
||||
|
||||
@@ -210,6 +255,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text", length=40, nullable=true)
|
||||
* @Assert\Regex(
|
||||
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
* @PhonenumberConstraint(
|
||||
* type="landline",
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $phonenumber = '';
|
||||
|
||||
@@ -218,6 +271,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text", length=40, nullable=true)
|
||||
* @Assert\Regex(
|
||||
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
* @PhonenumberConstraint(
|
||||
* type="mobile",
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $mobilenumber = '';
|
||||
|
||||
@@ -230,12 +291,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* cascade={"persist", "remove", "merge", "detach"},
|
||||
* orphanRemoval=true
|
||||
* )
|
||||
* @Assert\Valid(
|
||||
* traverse=true,
|
||||
* groups={"general", "creation"}
|
||||
* )
|
||||
*/
|
||||
private $otherPhoneNumbers;
|
||||
|
||||
//TO-ADD caseOpeningDate
|
||||
//TO-ADD nativeLanguag
|
||||
|
||||
/**
|
||||
* The person's spoken languages
|
||||
* @var ArrayCollection
|
||||
@@ -254,7 +316,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* @var Center
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center")
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
*/
|
||||
private $center;
|
||||
|
||||
@@ -353,6 +414,18 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
private $addresses;
|
||||
|
||||
/**
|
||||
* The current person address.
|
||||
*
|
||||
* This is computed through database and is optimized on database side.
|
||||
*
|
||||
* @var PersonCurrentAddress|null
|
||||
* @ORM\OneToOne(targetEntity=PersonCurrentAddress::class, mappedBy="person")
|
||||
*/
|
||||
private ?PersonCurrentAddress $currentPersonAddress = null;
|
||||
|
||||
/**
|
||||
* fullname canonical. Read-only field, which is calculated by
|
||||
* the database.
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
@@ -373,6 +446,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
private array $currentHouseholdAt = [];
|
||||
|
||||
/**
|
||||
* Read-only field, computed by the database
|
||||
*
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity=PersonHouseholdAddress::class,
|
||||
* mappedBy="person"
|
||||
@@ -390,8 +465,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
|
||||
/**
|
||||
* Person constructor.
|
||||
*
|
||||
* @param \DateTime|null $opening
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -404,6 +477,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
$this->householdAddresses = new ArrayCollection();
|
||||
$this->genderComment = new CommentEmbeddable();
|
||||
$this->maritalStatusComment = new CommentEmbeddable();
|
||||
$this->periodLocatedOn = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -501,6 +575,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
return $participation->getAccompanyingPeriod();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1179,13 +1255,31 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
return $this->addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `getCurrentPersonAddress` instead
|
||||
* @param DateTime|null $from
|
||||
* @return false|mixed|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getLastAddress(DateTime $from = null)
|
||||
{
|
||||
$from ??= new DateTime('now');
|
||||
return $this->getCurrentPersonAddress($from);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the address associated with the person at the given date
|
||||
*
|
||||
* @param DateTime|null $at
|
||||
* @return Address|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getCurrentPersonAddress(?\DateTime $at = null): ?Address
|
||||
{
|
||||
$at ??= new DateTime('now');
|
||||
|
||||
/** @var ArrayIterator $addressesIterator */
|
||||
$addressesIterator = $this->getAddresses()
|
||||
->filter(static fn (Address $address): bool => $address->getValidFrom() <= $from)
|
||||
->filter(static fn (Address $address): bool => $address->getValidFrom() <= $at)
|
||||
->getIterator();
|
||||
|
||||
$addressesIterator->uasort(
|
||||
@@ -1201,6 +1295,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* Validation callback that checks if the accompanying periods are valid
|
||||
*
|
||||
* This method add violation errors.
|
||||
*
|
||||
* @Assert\Callback(
|
||||
* groups={"accompanying_period_consistent"}
|
||||
* )
|
||||
*/
|
||||
public function isAccompanyingPeriodValid(ExecutionContextInterface $context)
|
||||
{
|
||||
@@ -1246,6 +1344,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
* two addresses with the same validFrom date)
|
||||
*
|
||||
* This method add violation errors.
|
||||
*
|
||||
* @Assert\Callback(
|
||||
* groups={"addresses_consistent"}
|
||||
* )
|
||||
*/
|
||||
public function isAddressesValid(ExecutionContextInterface $context)
|
||||
{
|
||||
@@ -1425,7 +1527,16 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
|
||||
public function getCurrentHouseholdAddress(?\DateTimeImmutable $at = null): ?Address
|
||||
{
|
||||
$at = $at === null ? new \DateTimeImmutable('today') : $at;
|
||||
if (
|
||||
NULL === $at
|
||||
||
|
||||
$at->format('Ymd') === (new \DateTime('today'))->format('Ymd')
|
||||
) {
|
||||
return $this->currentPersonAddress instanceof PersonCurrentAddress
|
||||
? $this->currentPersonAddress->getAddress() : NULL;
|
||||
}
|
||||
|
||||
// if not now, compute the date from history
|
||||
$criteria = new Criteria();
|
||||
$expr = Criteria::expr();
|
||||
|
||||
|
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Entity\Person;
|
||||
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Entity which associate person with his current address, through
|
||||
* household participation.
|
||||
*
|
||||
* The computation is optimized on database side.
|
||||
*
|
||||
* The validFrom and validTo properties are the intersection of
|
||||
* household membership and address validity. See @link{PersonHouseholdAddress}
|
||||
*
|
||||
* @ORM\Entity(readOnly=true)
|
||||
* @ORM\Table("view_chill_person_current_address")
|
||||
*/
|
||||
class PersonCurrentAddress
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\OneToOne(targetEntity=Person::class, inversedBy="currentPersonAddress")
|
||||
* @ORM\JoinColumn(name="person_id", referencedColumnName="id")
|
||||
*/
|
||||
protected Person $person;
|
||||
|
||||
/**
|
||||
* @ORM\OneToOne(targetEntity=Address::class)
|
||||
*/
|
||||
protected Address $address;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="valid_from", type="date_immutable")
|
||||
*/
|
||||
protected \DateTimeImmutable $validFrom;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="valid_to", type="date_immutable")
|
||||
*/
|
||||
protected ?\DateTimeImmutable $validTo;
|
||||
|
||||
/**
|
||||
* @return Person
|
||||
*/
|
||||
public function getPerson(): Person
|
||||
{
|
||||
return $this->person;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Address
|
||||
*/
|
||||
public function getAddress(): Address
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* This date is the intersection of household membership
|
||||
* and address validity
|
||||
*
|
||||
* @return \DateTimeImmutable
|
||||
*/
|
||||
public function getValidFrom(): \DateTimeImmutable
|
||||
{
|
||||
return $this->validFrom;
|
||||
}
|
||||
|
||||
/**
|
||||
* This date is the intersection of household membership
|
||||
* and address validity
|
||||
*
|
||||
* @return \DateTimeImmutable|null
|
||||
*/
|
||||
public function getValidTo(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->validTo;
|
||||
}
|
||||
}
|
@@ -21,7 +21,9 @@
|
||||
|
||||
namespace Chill\PersonBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Form\Event\CustomizeFormEvent;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@@ -33,6 +35,7 @@ use Chill\PersonBundle\Form\Type\GenderType;
|
||||
use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer;
|
||||
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
|
||||
use Chill\PersonBundle\Form\Type\PersonAltNameType;
|
||||
use Chill\MainBundle\Form\Type\Export\PickCenterType;
|
||||
|
||||
final class CreationPersonType extends AbstractType
|
||||
{
|
||||
@@ -40,10 +43,6 @@ final class CreationPersonType extends AbstractType
|
||||
// TODO: See if this is still valid and update accordingly.
|
||||
const NAME = 'chill_personbundle_person_creation';
|
||||
|
||||
const FORM_NOT_REVIEWED = 'not_reviewed';
|
||||
const FORM_REVIEWED = 'reviewed' ;
|
||||
const FORM_BEING_REVIEWED = 'being_reviewed';
|
||||
|
||||
/**
|
||||
*
|
||||
* @var CenterTransformer
|
||||
@@ -56,12 +55,16 @@ final class CreationPersonType extends AbstractType
|
||||
*/
|
||||
protected $configPersonAltNamesHelper;
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct(
|
||||
CenterTransformer $centerTransformer,
|
||||
ConfigPersonAltNamesHelper $configPersonAltNamesHelper
|
||||
ConfigPersonAltNamesHelper $configPersonAltNamesHelper,
|
||||
EventDispatcherInterface $dispatcher
|
||||
) {
|
||||
$this->centerTransformer = $centerTransformer;
|
||||
$this->configPersonAltNamesHelper = $configPersonAltNamesHelper;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +82,9 @@ final class CreationPersonType extends AbstractType
|
||||
->add('gender', GenderType::class, array(
|
||||
'required' => true, 'placeholder' => null
|
||||
))
|
||||
->add('center', CenterType::class)
|
||||
->add('center', CenterType::class, [
|
||||
'required' => false
|
||||
])
|
||||
;
|
||||
|
||||
if ($this->configPersonAltNamesHelper->hasAltNames()) {
|
||||
@@ -87,6 +92,11 @@ final class CreationPersonType extends AbstractType
|
||||
'by_reference' => false
|
||||
]);
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch(
|
||||
new CustomizeFormEvent(static::class, $builder),
|
||||
CustomizeFormEvent::NAME
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -20,6 +20,9 @@
|
||||
|
||||
namespace Chill\PersonBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Chill\PersonBundle\Entity\MaritalStatus;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer;
|
||||
@@ -35,15 +38,13 @@ use Chill\MainBundle\Form\Type\Select2ChoiceType;
|
||||
*/
|
||||
class Select2MaritalStatusType extends AbstractType
|
||||
{
|
||||
/** @var RequestStack */
|
||||
private $requestStack;
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
/** @var ObjectManager */
|
||||
private $em;
|
||||
private TranslatableStringHelper $translatableStringHelper;
|
||||
|
||||
public function __construct(RequestStack $requestStack,ObjectManager $em)
|
||||
public function __construct(TranslatableStringHelper $translatableStringHelper, EntityManagerInterface $em)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
@@ -63,18 +64,17 @@ class Select2MaritalStatusType extends AbstractType
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$locale = $this->requestStack->getCurrentRequest()->getLocale();
|
||||
$maritalStatuses = $this->em->getRepository('Chill\PersonBundle\Entity\MaritalStatus')->findAll();
|
||||
$choices = array();
|
||||
|
||||
foreach ($maritalStatuses as $ms) {
|
||||
$choices[$ms->getId()] = $ms->getName()[$locale];
|
||||
$choices[$ms->getId()] = $this->translatableStringHelper->localize($ms->getName());
|
||||
}
|
||||
|
||||
asort($choices, SORT_STRING | SORT_FLAG_CASE);
|
||||
|
||||
$resolver->setDefaults(array(
|
||||
'class' => 'Chill\PersonBundle\Entity\MaritalStatus',
|
||||
'class' => MaritalStatus::class,
|
||||
'choices' => array_combine(array_values($choices),array_keys($choices))
|
||||
));
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$menu->addChild($this->translator->trans('Accompanying Course Details'), [
|
||||
$menu->addChild($this->translator->trans('Accompanying Course History'), [
|
||||
'route' => 'chill_person_accompanying_course_history',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId()
|
||||
|
@@ -18,14 +18,17 @@
|
||||
namespace Chill\PersonBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* Add menu entrie to person menu.
|
||||
*
|
||||
*
|
||||
* Menu entries added :
|
||||
*
|
||||
*
|
||||
* - person details ;
|
||||
* - accompanying period (if `visible`)
|
||||
*
|
||||
@@ -37,21 +40,25 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
* @var string 'visible' or 'hidden'
|
||||
*/
|
||||
protected $showAccompanyingPeriod;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
|
||||
private Security $security;
|
||||
|
||||
public function __construct(
|
||||
$showAccompanyingPeriod,
|
||||
ParameterBagInterface $parameterBag,
|
||||
Security $security,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->showAccompanyingPeriod = $showAccompanyingPeriod;
|
||||
$this->showAccompanyingPeriod = $parameterBag->get('chill_person.accompanying_period');
|
||||
$this->security = $security;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
$menu->addChild($this->translator->trans('Person details'), [
|
||||
@@ -83,8 +90,10 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
->setExtras([
|
||||
'order' => 99999
|
||||
]);
|
||||
|
||||
if ($this->showAccompanyingPeriod === 'visible') {
|
||||
|
||||
if ($this->showAccompanyingPeriod === 'visible'
|
||||
&& $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person'])
|
||||
) {
|
||||
$menu->addChild($this->translator->trans('Accompanying period list'), [
|
||||
'route' => 'chill_person_accompanying_period_list',
|
||||
'routeParameters' => [
|
||||
|
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodACLAwareRepositoryInterface
|
||||
{
|
||||
private AccompanyingPeriodRepository $accompanyingPeriodRepository;
|
||||
private Security $security;
|
||||
private AuthorizationHelper $authorizationHelper;
|
||||
private CenterResolverDispatcher $centerResolverDispatcher;
|
||||
|
||||
public function __construct(AccompanyingPeriodRepository $accompanyingPeriodRepository, Security $security, AuthorizationHelper $authorizationHelper, CenterResolverDispatcher $centerResolverDispatcher)
|
||||
{
|
||||
$this->accompanyingPeriodRepository = $accompanyingPeriodRepository;
|
||||
$this->security = $security;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||
}
|
||||
|
||||
public function findByPerson(
|
||||
Person $person,
|
||||
string $role,
|
||||
?array $orderBy = [],
|
||||
int $limit = null,
|
||||
int $offset = null
|
||||
): array {
|
||||
$qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap');
|
||||
$scopes = $this->authorizationHelper
|
||||
->getReachableCircles($this->security->getUser(), $role,
|
||||
$this->centerResolverDispatcher->resolveCenter($person));
|
||||
|
||||
if (0 === count($scopes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$qb
|
||||
->join('ap.participations', 'participation')
|
||||
->where($qb->expr()->eq('participation.person', ':person'))
|
||||
->andWhere(
|
||||
$qb->expr()->orX(
|
||||
'ap.confidential = FALSE',
|
||||
$qb->expr()->eq('ap.user', ':user')
|
||||
)
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->neq('ap.step', ':draft'),
|
||||
$qb->expr()->eq('ap.createdBy', ':creator')
|
||||
)
|
||||
)
|
||||
->setParameter('draft', AccompanyingPeriod::STEP_DRAFT)
|
||||
->setParameter('person', $person)
|
||||
->setParameter('user', $this->security->getUser())
|
||||
->setParameter('creator', $this->security->getUser())
|
||||
;
|
||||
// add join condition for scopes
|
||||
$orx = $qb->expr()->orX(
|
||||
$qb->expr()->eq('ap.step', ':draft')
|
||||
);
|
||||
|
||||
foreach ($scopes as $key => $scope) {
|
||||
$orx->add($qb->expr()->isMemberOf(':scope_'.$key, 'ap.scopes'));
|
||||
$qb->setParameter('scope_'.$key, $scope);
|
||||
}
|
||||
$qb->andWhere($orx);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
interface AccompanyingPeriodACLAwareRepositoryInterface
|
||||
{
|
||||
public function findByPerson(
|
||||
Person $person,
|
||||
string $role,
|
||||
?array $orderBy = [],
|
||||
int $limit = null,
|
||||
int $offset = null
|
||||
): array;
|
||||
}
|
@@ -59,6 +59,11 @@ final class AccompanyingPeriodRepository implements ObjectRepository
|
||||
return $this->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder
|
||||
{
|
||||
return $this->repository->createQueryBuilder($alias, $indexBy);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
{
|
||||
return AccompanyingPeriod::class;
|
||||
|
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Repository\CountryRepository;
|
||||
use Chill\MainBundle\Search\ParsingException;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
|
||||
final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterface
|
||||
{
|
||||
private Security $security;
|
||||
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private CountryRepository $countryRepository;
|
||||
|
||||
private AuthorizationHelper $authorizationHelper;
|
||||
|
||||
public function __construct(
|
||||
Security $security,
|
||||
EntityManagerInterface $em,
|
||||
CountryRepository $countryRepository,
|
||||
AuthorizationHelper $authorizationHelper
|
||||
) {
|
||||
$this->security = $security;
|
||||
$this->em = $em;
|
||||
$this->countryRepository = $countryRepository;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|Person[]
|
||||
* @throws NonUniqueResultException
|
||||
* @throws ParsingException
|
||||
*/
|
||||
public function findBySearchCriteria(
|
||||
int $start,
|
||||
int $limit,
|
||||
bool $simplify = false,
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
): array {
|
||||
$qb = $this->createSearchQuery($default, $firstname, $lastname,
|
||||
$birthdate, $birthdateBefore, $birthdateAfter, $gender,
|
||||
$countryCode);
|
||||
$this->addACLClauses($qb, 'p');
|
||||
|
||||
return $this->getQueryResult($qb, 'p', $simplify, $limit, $start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to prepare and return the search query for PersonACL.
|
||||
*
|
||||
* This method replace the select clause with required parameters, depending on the
|
||||
* "simplify" parameter. It also add query limits.
|
||||
*
|
||||
* The given alias must represent the person alias.
|
||||
*
|
||||
* @return array|Person[]
|
||||
*/
|
||||
public function getQueryResult(QueryBuilder $qb, string $alias, bool $simplify, int $limit, int $start): array
|
||||
{
|
||||
if ($simplify) {
|
||||
$qb->select(
|
||||
$alias.'.id',
|
||||
$qb->expr()->concat(
|
||||
$alias.'.firstName',
|
||||
$qb->expr()->literal(' '),
|
||||
$alias.'.lastName'
|
||||
).'AS text'
|
||||
);
|
||||
} else {
|
||||
$qb->select($alias);
|
||||
}
|
||||
|
||||
$qb
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($start);
|
||||
|
||||
//order by firstname, lastname
|
||||
$qb
|
||||
->orderBy($alias.'.firstName')
|
||||
->addOrderBy($alias.'.lastName');
|
||||
|
||||
if ($simplify) {
|
||||
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
|
||||
} else {
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
public function countBySearchCriteria(
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
): int {
|
||||
$qb = $this->createSearchQuery($default, $firstname, $lastname,
|
||||
$birthdate, $birthdateBefore, $birthdateAfter, $gender,
|
||||
$countryCode);
|
||||
$this->addACLClauses($qb, 'p');
|
||||
|
||||
return $this->getCountQueryResult($qb,'p');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to prepare and return the count for search query
|
||||
*
|
||||
* This method replace the select clause with required parameters, depending on the
|
||||
* "simplify" parameter.
|
||||
*
|
||||
* The given alias must represent the person alias in the query builder.
|
||||
*/
|
||||
public function getCountQueryResult(QueryBuilder $qb, $alias): int
|
||||
{
|
||||
$qb->select('COUNT('.$alias.'.id)');
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findBySimilaritySearch(string $pattern, int $firstResult,
|
||||
int $maxResult, bool $simplify = false)
|
||||
{
|
||||
$qb = $this->createSimilarityQuery($pattern);
|
||||
$this->addACLClauses($qb, 'sp');
|
||||
|
||||
return $this->getQueryResult($qb, 'sp', $simplify, $maxResult, $firstResult);
|
||||
}
|
||||
|
||||
public function countBySimilaritySearch(string $pattern)
|
||||
{
|
||||
$qb = $this->createSimilarityQuery($pattern);
|
||||
$this->addACLClauses($qb, 'sp');
|
||||
|
||||
return $this->getCountQueryResult($qb, 'sp');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a search query without ACL
|
||||
*
|
||||
* The person alias is a "p"
|
||||
*
|
||||
* @param string|null $default
|
||||
* @param string|null $firstname
|
||||
* @param string|null $lastname
|
||||
* @param \DateTime|null $birthdate
|
||||
* @param \DateTime|null $birthdateBefore
|
||||
* @param \DateTime|null $birthdateAfter
|
||||
* @param string|null $gender
|
||||
* @param string|null $countryCode
|
||||
* @return QueryBuilder
|
||||
* @throws NonUniqueResultException
|
||||
* @throws ParsingException
|
||||
*/
|
||||
public function createSearchQuery(
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
): QueryBuilder {
|
||||
|
||||
if (!$this->security->getUser() instanceof User) {
|
||||
throw new \RuntimeException("Search must be performed by a valid user");
|
||||
}
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$qb->from(Person::class, 'p');
|
||||
|
||||
if (NULL !== $firstname) {
|
||||
$qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.firstName))', ':firstname'))
|
||||
->setParameter('firstname', '%'.$firstname.'%');
|
||||
}
|
||||
|
||||
if (NULL !== $lastname) {
|
||||
$qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.lastName))', ':lastname'))
|
||||
->setParameter('lastname', '%'.$lastname.'%');
|
||||
}
|
||||
|
||||
if (NULL !== $birthdate) {
|
||||
$qb->andWhere($qb->expr()->eq('s.birthdate', ':birthdate'))
|
||||
->setParameter('birthdate', $birthdate);
|
||||
}
|
||||
|
||||
if (NULL !== $birthdateAfter) {
|
||||
$qb->andWhere($qb->expr()->gt('p.birthdate', ':birthdateafter'))
|
||||
->setParameter('birthdateafter', $birthdateAfter);
|
||||
}
|
||||
|
||||
if (NULL !== $birthdateBefore) {
|
||||
$qb->andWhere($qb->expr()->lt('p.birthdate', ':birthdatebefore'))
|
||||
->setParameter('birthdatebefore', $birthdateBefore);
|
||||
}
|
||||
|
||||
if (NULL !== $gender) {
|
||||
$qb->andWhere($qb->expr()->eq('p.gender', ':gender'))
|
||||
->setParameter('gender', $gender);
|
||||
}
|
||||
|
||||
if (NULL !== $countryCode) {
|
||||
try {
|
||||
$country = $this->countryRepository->findOneBy(['countryCode' => $countryCode]);
|
||||
} catch (NoResultException $ex) {
|
||||
throw new ParsingException('The country code "'.$countryCode.'" '
|
||||
. ', used in nationality, is unknow', 0, $ex);
|
||||
} catch (NonUniqueResultException $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$qb->andWhere($qb->expr()->eq('p.nationality', ':nationality'))
|
||||
->setParameter('nationality', $country);
|
||||
}
|
||||
|
||||
if (NULL !== $default) {
|
||||
$grams = explode(' ', $default);
|
||||
|
||||
foreach($grams as $key => $gram) {
|
||||
$qb->andWhere($qb->expr()
|
||||
->like('p.fullnameCanonical', 'UNACCENT(LOWER(:default_'.$key.'))'))
|
||||
->setParameter('default_'.$key, '%'.$gram.'%');
|
||||
}
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function addACLClauses(QueryBuilder $qb, string $personAlias): void
|
||||
{
|
||||
// restrict center for security
|
||||
$reachableCenters = $this->authorizationHelper
|
||||
->getReachableCenters($this->security->getUser(), 'CHILL_PERSON_SEE');
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()
|
||||
->in($personAlias.'.center', ':centers'),
|
||||
$qb->expr()
|
||||
->isNull($personAlias.'.center')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('centers', $reachableCenters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query for searching by similarity.
|
||||
*
|
||||
* The person alias is "sp".
|
||||
*
|
||||
* @param $pattern
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function createSimilarityQuery($pattern): QueryBuilder
|
||||
{
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
|
||||
$qb->from(Person::class, 'sp');
|
||||
|
||||
$grams = explode(' ', $pattern);
|
||||
|
||||
foreach($grams as $key => $gram) {
|
||||
$qb->andWhere('STRICT_WORD_SIMILARITY_OPS(:default_'.$key.', sp.fullnameCanonical) = TRUE')
|
||||
->setParameter('default_'.$key, '%'.$gram.'%');
|
||||
|
||||
// remove the perfect matches
|
||||
$qb->andWhere($qb->expr()
|
||||
->notLike('sp.fullnameCanonical', 'UNACCENT(LOWER(:not_default_'.$key.'))'))
|
||||
->setParameter('not_default_'.$key, '%'.$gram.'%');
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Search\ParsingException;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
|
||||
interface PersonACLAwareRepositoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array|Person[]
|
||||
* @throws NonUniqueResultException
|
||||
* @throws ParsingException
|
||||
*/
|
||||
public function findBySearchCriteria(
|
||||
int $start,
|
||||
int $limit,
|
||||
bool $simplify = false,
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
): array;
|
||||
|
||||
public function countBySearchCriteria(
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
);
|
||||
|
||||
public function findBySimilaritySearch(
|
||||
string $pattern,
|
||||
int $firstResult,
|
||||
int $maxResult,
|
||||
bool $simplify = false
|
||||
);
|
||||
|
||||
public function countBySimilaritySearch(string $pattern);
|
||||
}
|
@@ -23,9 +23,11 @@ use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use UnexpectedValueException;
|
||||
|
||||
|
||||
final class PersonRepository
|
||||
final class PersonRepository implements ObjectRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
@@ -44,6 +46,26 @@ final class PersonRepository
|
||||
return $this->repository->findBy(['id' => $ids]);
|
||||
}
|
||||
|
||||
public function findAll()
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria)
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
{
|
||||
return Person::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $centers
|
||||
* @param $firstResult
|
||||
|
@@ -87,7 +87,6 @@ div.person-view {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ACCOMPANYING_COURSE CONTEXT
|
||||
* Header custom for Accompanying Course
|
||||
@@ -125,7 +124,6 @@ abbr.referrer {
|
||||
align-self: center; // in flex context
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* HOUSEHOLD CONTEXT
|
||||
* Header custom for Household
|
||||
@@ -157,12 +155,19 @@ div.banner {
|
||||
span.badge-member {
|
||||
flex-shrink: 0; flex-grow: 0; flex-basis: auto;
|
||||
color: $white;
|
||||
border: 1px solid #ffffff3b;
|
||||
border: 1px solid transparentize($white, 0.75);
|
||||
border-bottom: 3px solid transparentize( shade-color( $chill-green, 20%), 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 0.4em 0.8em;
|
||||
padding: 0.2em 0.7em;
|
||||
margin-bottom: 0.2em;
|
||||
margin-right: 0.3em;
|
||||
&.holder { order: -1; }
|
||||
|
||||
&.holder {
|
||||
order: -1;
|
||||
.fa-holder .text-success {
|
||||
color: transparentize( shade-color( $chill-green, 20%), 0.3) !important;
|
||||
}
|
||||
}
|
||||
&.child { order: 2; }
|
||||
}
|
||||
}
|
||||
@@ -179,3 +184,66 @@ div.banner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.household-resume {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
div.col-address {
|
||||
font-size: 120%;
|
||||
padding-left: 1em;
|
||||
|
||||
}
|
||||
div.col-comment {
|
||||
//padding: 0;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> * > * {
|
||||
& > .chill-user-quote {
|
||||
margin: 1.5em -1.67em 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* BADGES, MARKS, PINS
|
||||
* for chill person theme
|
||||
*/
|
||||
|
||||
// chill person badges
|
||||
span.badge-person,
|
||||
span.badge-thirdparty {
|
||||
display: inline-block;
|
||||
padding: 0 0.5em !important;
|
||||
background-color: $white;
|
||||
color: $dark;
|
||||
border: 1px solid $chill-ll-gray;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
border-radius: 6px;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
span.badge-person {
|
||||
border-bottom-color: $chill-green;
|
||||
}
|
||||
// todo: move in thirdparty
|
||||
span.badge-thirdparty {
|
||||
border-bottom-color: shade-color($chill-pink, 10%);
|
||||
}
|
||||
|
||||
// household holder mark
|
||||
span.fa-holder {
|
||||
width: 1em;
|
||||
margin: -10px 0.3em -8px 0;
|
||||
i:last-child {
|
||||
font-weight: 900;
|
||||
color: white;
|
||||
font-size: 70%;
|
||||
font-family: "Open Sans Extrabold";
|
||||
}
|
||||
}
|
||||
|
@@ -26,26 +26,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// specific chill badges
|
||||
span.badge-person,
|
||||
span.badge-thirdparty {
|
||||
display: inline-block;
|
||||
padding: 0 0.5em !important;
|
||||
background-color: $white;
|
||||
color: $dark;
|
||||
border: 1px solid $chill-ll-gray;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
border-radius: 6px;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
span.badge-person {
|
||||
border-bottom-color: $chill-green;
|
||||
}
|
||||
// todo: move in thirdparty
|
||||
span.badge-thirdparty {
|
||||
border-bottom-color: shade-color($chill-pink, 10%);
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
<origin-demand></origin-demand>
|
||||
<requestor></requestor>
|
||||
<social-issue></social-issue>
|
||||
<scopes></scopes>
|
||||
<referrer></referrer>
|
||||
<resources></resources>
|
||||
<comment v-if="accompanyingCourse.step === 'DRAFT'"></comment>
|
||||
@@ -32,6 +33,7 @@ import PersonsAssociated from './components/PersonsAssociated.vue';
|
||||
import Requestor from './components/Requestor.vue';
|
||||
import SocialIssue from './components/SocialIssue.vue';
|
||||
import CourseLocation from './components/CourseLocation.vue';
|
||||
import Scopes from './components/Scopes.vue';
|
||||
import Referrer from './components/Referrer.vue';
|
||||
import Resources from './components/Resources.vue';
|
||||
import Comment from './components/Comment.vue';
|
||||
@@ -47,6 +49,7 @@ export default {
|
||||
Requestor,
|
||||
SocialIssue,
|
||||
CourseLocation,
|
||||
Scopes,
|
||||
Referrer,
|
||||
Resources,
|
||||
Comment,
|
||||
@@ -77,7 +80,7 @@ export default {
|
||||
left: -22px;
|
||||
top: 4px;
|
||||
}
|
||||
a[name^="section"] {
|
||||
a[id^="section"] {
|
||||
position: absolute;
|
||||
top: -2.5em; // reference for stickNav
|
||||
}
|
||||
@@ -93,7 +96,8 @@ export default {
|
||||
}
|
||||
& > div {
|
||||
margin: 1em 3em 0;
|
||||
&.flex-table {
|
||||
&.flex-table,
|
||||
&.flex-bloc {
|
||||
margin: 1em 0 0;
|
||||
}
|
||||
}
|
||||
|
@@ -191,7 +191,49 @@ const getListOrigins = () => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw { msg: 'Error while retriving origin\'s list.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const addScope = (id, scope) => {
|
||||
const url = `/api/1.0/person/accompanying-course/${id}/scope.json`;
|
||||
console.log(url);
|
||||
console.log(scope);
|
||||
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
id: scope.id,
|
||||
type: scope.type,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw { msg: 'Error while adding scope', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
|
||||
});
|
||||
};
|
||||
|
||||
const removeScope = (id, scope) => {
|
||||
const url = `/api/1.0/person/accompanying-course/${id}/scope.json`;
|
||||
console.log(url);
|
||||
console.log(scope);
|
||||
|
||||
return fetch(url, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({
|
||||
id: scope.id,
|
||||
type: scope.type,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw { msg: 'Error while adding scope', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
getAccompanyingCourse,
|
||||
@@ -204,5 +246,7 @@ export {
|
||||
getUsers,
|
||||
whoami,
|
||||
getListOrigins,
|
||||
postSocialIssue
|
||||
postSocialIssue,
|
||||
addScope,
|
||||
removeScope,
|
||||
};
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<h2 class="modal-title">{{ $t('courselocation.sure') }}</h2>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<show-address :address="person.current_household_address"></show-address>
|
||||
<address-render-box :address="person.current_household_address"></address-render-box>
|
||||
<p>{{ $t('courselocation.sure_description') }}</p>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
@@ -28,12 +28,12 @@
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||
import ShowAddress from "ChillMainAssets/vuejs/Address/components/ShowAddress";
|
||||
import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue";
|
||||
|
||||
export default {
|
||||
name: "ButtonLocation",
|
||||
components: {
|
||||
ShowAddress,
|
||||
AddressRenderBox,
|
||||
Modal,
|
||||
},
|
||||
props: ['person'],
|
||||
@@ -54,8 +54,8 @@ export default {
|
||||
assignAddress() {
|
||||
//console.log('assignAddress id', this.person.current_household_address);
|
||||
let payload = {
|
||||
entity: this.context.entity.type,
|
||||
entityId: this.context.entity.id,
|
||||
target: this.context.target.name,
|
||||
targetId: this.context.target.id,
|
||||
locationStatusTo: 'person',
|
||||
personId: this.person.id
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a name="section-80"></a>{{ $t('comment.title') }}</h2>
|
||||
<h2><a id="section-90"></a>{{ $t('comment.title') }}</h2>
|
||||
|
||||
<!--div class="error flash_message" v-if="errors.length > 0">
|
||||
{{ errors[0] }}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a name="section-90"></a>
|
||||
<h2><a id="section-100"></a>
|
||||
{{ $t('confirm.title') }}
|
||||
</h2>
|
||||
<div>
|
||||
@@ -88,6 +88,10 @@ export default {
|
||||
socialIssue: {
|
||||
msg: 'confirm.socialIssue_not_valid',
|
||||
anchor: '#section-50'
|
||||
},
|
||||
scopes: {
|
||||
msg: 'confirm.set_a_scope',
|
||||
anchor: '#section-65'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,9 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a name="section-20"></a>
|
||||
<h2><a id="section-20"></a>
|
||||
{{ $t('courselocation.title') }}
|
||||
</h2>
|
||||
|
||||
<!-- {# include vue_address component #} -->
|
||||
<div v-for="error in displayErrors" class="alert alert-danger my-2">
|
||||
{{ error }}
|
||||
</div>
|
||||
@@ -17,9 +16,9 @@
|
||||
|
||||
<div class="flex-table" v-if="accompanyingCourse.location">
|
||||
<div class="item-bloc">
|
||||
<show-address
|
||||
<address-render-box
|
||||
:address="accompanyingCourse.location">
|
||||
</show-address>
|
||||
</address-render-box>
|
||||
|
||||
<div v-if="isPersonLocation" class="alert alert-secondary separator">
|
||||
<label class="col-form-label">
|
||||
@@ -41,8 +40,7 @@
|
||||
:context="context"
|
||||
:key="addAddress.type"
|
||||
:options="addAddress.options"
|
||||
:result="addAddress.result"
|
||||
@submitAddress="submitTemporaryAddress"
|
||||
:addressChangedCallback="submitTemporaryAddress"
|
||||
ref="addAddress">
|
||||
</add-address>
|
||||
</li>
|
||||
@@ -63,13 +61,13 @@
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import AddAddress from 'ChillMainAssets/vuejs/Address/components/AddAddress.vue';
|
||||
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
|
||||
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||
|
||||
export default {
|
||||
name: "CourseLocation",
|
||||
components: {
|
||||
AddAddress,
|
||||
ShowAddress
|
||||
AddressRenderBox
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -89,10 +87,12 @@ export default {
|
||||
edit: 'courselocation.edit_temporary_address'
|
||||
},
|
||||
/// Display each step in page or Modal
|
||||
bindModal: {
|
||||
//step1: false, step2: false
|
||||
},
|
||||
hideDateFrom: true
|
||||
openPanesInModal: true,
|
||||
// Use Date fields
|
||||
//useDate: {
|
||||
// validFrom: true
|
||||
//},
|
||||
hideAddress: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,8 +118,8 @@ export default {
|
||||
methods: {
|
||||
initAddressContext() {
|
||||
let context = {
|
||||
entity: {
|
||||
type: this.accompanyingCourse.type,
|
||||
target: {
|
||||
name: this.accompanyingCourse.type,
|
||||
id: this.accompanyingCourse.id
|
||||
},
|
||||
edit: false,
|
||||
@@ -132,31 +132,30 @@ export default {
|
||||
this.$store.commit('setAddressContext', context);
|
||||
},
|
||||
removeAddress() {
|
||||
//console.log('remove address');
|
||||
let payload = {
|
||||
entity: this.context.entity.type,
|
||||
entityId: this.context.entity.id,
|
||||
target: this.context.target.name,
|
||||
targetId: this.context.target.id,
|
||||
locationStatusTo: 'none'
|
||||
};
|
||||
//console.log('remove address');
|
||||
this.$store.dispatch('updateLocation', payload);
|
||||
},
|
||||
displayErrors() {
|
||||
return this.$refs.addAddress.errorMsg;
|
||||
},
|
||||
submitTemporaryAddress() {
|
||||
//console.log('@@@ click on Submit Temporary Address Button');
|
||||
let payload = this.$refs.addAddress.submitNewAddress();
|
||||
submitTemporaryAddress(payload) {
|
||||
//console.log('@@@ click on Submit Temporary Address Button', payload);
|
||||
payload['locationStatusTo'] = 'address'; // <== temporary, not none, not person
|
||||
this.$store.dispatch('updateLocation', payload);
|
||||
this.$store.commit('setEditContextTrue');
|
||||
this.$store.commit('setEditContextTrue', payload);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
created() {
|
||||
this.initAddressContext();
|
||||
|
||||
//console.log('ac.locationStatus', this.accompanyingCourse.locationStatus);
|
||||
//console.log('ac.location (temporary location)', this.accompanyingCourse.location);
|
||||
//console.log('ac.personLocation', this.accompanyingCourse.personLocation);
|
||||
console.log('ac.locationStatus', this.accompanyingCourse.locationStatus);
|
||||
console.log('ac.location (temporary location)', this.accompanyingCourse.location);
|
||||
console.log('ac.personLocation', this.accompanyingCourse.personLocation);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a name="section-30"></a>{{ $t('origin.title') }}</h2>
|
||||
<h2><a id="section-30"></a>{{ $t('origin.title') }}</h2>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="selectOrigin">
|
||||
|
@@ -1,17 +1,45 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a name="section-10"></a>{{ $t('persons_associated.title')}}</h2>
|
||||
<h2><a id="section-10"></a>{{ $t('persons_associated.title')}}</h2>
|
||||
|
||||
<div v-if="participations.length > 0">
|
||||
<div v-if="currentParticipations.length > 0">
|
||||
<label class="col-form-label">{{ $tc('persons_associated.counter', counter) }}</label>
|
||||
</div>
|
||||
<div v-else>
|
||||
<label class="chill-no-data-statement">{{ $tc('persons_associated.counter', counter) }}</label>
|
||||
</div>
|
||||
|
||||
<div v-if="participationWithoutHousehold.length > 0" class="alert alert-warning no-household">
|
||||
<i class="fa fa-warning fa-2x"></i>
|
||||
<form method="GET" action="/fr/person/household/members/editor">
|
||||
<div class="float-button bottom"><div class="box">
|
||||
<div class="action">
|
||||
<button class="btn btn-update" type="submit">{{ $t('persons_associated.update_household') }}</button>
|
||||
</div>
|
||||
<p class="mb-3">{{ $t('persons_associated.person_without_household_warning') }}</p>
|
||||
<div class="form-check" v-for="p in participationWithoutHousehold">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="hasNoHousehold"
|
||||
name="persons[]"
|
||||
checked="checked"
|
||||
:id="p.person.id"
|
||||
:value="p.person.id"
|
||||
/>
|
||||
<label class="form-check-label" for="hasNoHousehold">
|
||||
{{ p.person.text }}
|
||||
</label>
|
||||
</div>
|
||||
<input type="hidden" name="expand_suggestions" value="true">
|
||||
<input type="hidden" name="returnPath" :value="getReturnPath">
|
||||
<input type="hidden" name="accompanying_period_id" :value="courseId">
|
||||
</div></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="flex-table mb-3">
|
||||
<participation-item
|
||||
v-for="participation in participations"
|
||||
v-for="participation in currentParticipations"
|
||||
v-bind:participation="participation"
|
||||
v-bind:key="participation.id"
|
||||
@remove="removeParticipation"
|
||||
@@ -56,10 +84,24 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
participations: state => state.accompanyingCourse.participations,
|
||||
counter: state => state.accompanyingCourse.participations.length
|
||||
}),
|
||||
computed: {
|
||||
...mapState({
|
||||
courseId: state => state.accompanyingCourse.id,
|
||||
participations: state => state.accompanyingCourse.participations
|
||||
}),
|
||||
currentParticipations() {
|
||||
return this.participations.filter(p => p.endDate === null)
|
||||
},
|
||||
counter() {
|
||||
return this.currentParticipations.length;
|
||||
},
|
||||
participationWithoutHousehold() {
|
||||
return this.currentParticipations.filter(p => p.person.current_household_id === null);
|
||||
},
|
||||
getReturnPath() {
|
||||
return window.location.pathname + window.location.search + window.location.hash;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeParticipation(item) {
|
||||
//console.log('@@ CLICK remove participation: item', item);
|
||||
@@ -81,3 +123,30 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
div#accompanying-course {
|
||||
div.vue-component {
|
||||
& > div.alert {
|
||||
margin: 0 0 -1em;
|
||||
}
|
||||
div.no-household {
|
||||
padding-bottom: 1.5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
& > i {
|
||||
flex-basis: 1.5em; flex-grow: 0; flex-shrink: 0;
|
||||
padding-top: 0.2em;
|
||||
opacity: 0.75;
|
||||
}
|
||||
& > form {
|
||||
flex-basis: auto;
|
||||
div.action {
|
||||
button.btn-update {
|
||||
margin-right: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,15 +1,17 @@
|
||||
<template>
|
||||
<person-render-box
|
||||
<person-render-box render="bloc"
|
||||
:options="{
|
||||
addInfo : true,
|
||||
addId : false,
|
||||
addEntity: false,
|
||||
addLink: false,
|
||||
addHouseholdLink: true,
|
||||
addAltNames: true,
|
||||
addAge : false,
|
||||
addAge : true,
|
||||
hLevel : 3,
|
||||
}"
|
||||
:person="participation.person">
|
||||
:person="participation.person"
|
||||
:returnPath="getAccompanyingCourseReturnPath">
|
||||
|
||||
<template v-slot:record-actions>
|
||||
<ul class="record_actions">
|
||||
@@ -40,34 +42,54 @@
|
||||
<li>
|
||||
<button v-if="!participation.endDate"
|
||||
class="btn btn-sm btn-remove"
|
||||
v-bind:title="$t('action.remove')"
|
||||
@click.prevent="$emit('close', participation)">
|
||||
v-bind:title="$t('persons_associated.leave_course')"
|
||||
@click="modal.showModal = true">
|
||||
</button>
|
||||
<button v-else
|
||||
class="btn btn-sm btn-remove disabled"></button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</template>
|
||||
</person-render-box>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal.showModal" :modalDialogClass="modal.modalDialogClass" @close="modal.showModal = false">
|
||||
<template v-slot:header>
|
||||
<h2 class="modal-title">{{ $t('persons_associated.sure') }}</h2>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<p>{{ $t('persons_associated.sure_description') }}</p>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button class="btn btn-danger" @click.prevent="$emit('close', participation)">
|
||||
{{ $t('persons_associated.ok') }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/_components/OnTheFly.vue';
|
||||
import ButtonLocation from '../ButtonLocation.vue';
|
||||
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||
|
||||
export default {
|
||||
name: 'ParticipationItem',
|
||||
components: {
|
||||
OnTheFly,
|
||||
ButtonLocation,
|
||||
PersonRenderBox
|
||||
PersonRenderBox,
|
||||
Modal
|
||||
},
|
||||
props: ['participation'],
|
||||
emits: ['remove', 'close'],
|
||||
data() {
|
||||
return {
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-centered modal-md"
|
||||
},
|
||||
PersonRenderBox: {
|
||||
participation: 'participation',
|
||||
options: {
|
||||
@@ -86,6 +108,9 @@ export default {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getAccompanyingCourseReturnPath() {
|
||||
return `fr/parcours/${this.$store.state.accompanyingCourse.id}/edit#section-10`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,10 +118,7 @@ export default {
|
||||
/*
|
||||
* dates of participation
|
||||
*
|
||||
* :title="$t('persons_associated.date_start_to_end', {
|
||||
* start: $d(participation.startDate.datetime, 'short'),
|
||||
* end: $d(participation.endDate.datetime, 'short')
|
||||
* })"
|
||||
*
|
||||
*
|
||||
* <tr>
|
||||
* <td><span v-if="participation.startDate">
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a name="section-60"></a>{{ $t('referrer.title') }}</h2>
|
||||
<h2><a id="section-70"></a>{{ $t('referrer.title') }}</h2>
|
||||
|
||||
<div>
|
||||
<label class="col-form-label" for="selectReferrer">
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
|
||||
<h2><a name="section-40"></a>{{ $t('requestor.title') }}</h2>
|
||||
<h2><a id="section-40"></a>{{ $t('requestor.title') }}</h2>
|
||||
|
||||
<div v-if="accompanyingCourse.requestor" class="flex-table">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
addLink: false,
|
||||
addId: false,
|
||||
addEntity: true,
|
||||
addInfo: true,
|
||||
addInfo: false,
|
||||
hLevel: 3,
|
||||
isMultiline: true
|
||||
}"
|
||||
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
</third-party-render-box>
|
||||
|
||||
<person-render-box v-else-if="accompanyingCourse.requestor.type == 'person'"
|
||||
<person-render-box render="bloc" v-else-if="accompanyingCourse.requestor.type == 'person'"
|
||||
:person="accompanyingCourse.requestor"
|
||||
:options="{
|
||||
addLink: false,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
|
||||
<h2><a name="section-70"></a>{{ $t('resources.title')}}</h2>
|
||||
<h2><a id="section-80"></a>{{ $t('resources.title')}}</h2>
|
||||
|
||||
<div v-if="resources.length > 0">
|
||||
<label class="col-form-label">{{ $tc('resources.counter', counter) }}</label>
|
||||
@@ -10,7 +10,7 @@
|
||||
<label class="chill-no-data-statement">{{ $tc('resources.counter', counter) }}</label>
|
||||
</div>
|
||||
|
||||
<div class="flex-table mb-3">
|
||||
<div class="flex-bloc mb-3">
|
||||
<resource-item
|
||||
v-for="resource in resources"
|
||||
v-bind:resource="resource"
|
||||
@@ -76,3 +76,11 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
div.flex-bloc {
|
||||
div.item-bloc {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<person-render-box
|
||||
<person-render-box render="bloc"
|
||||
v-if="resource.resource.type === 'person'"
|
||||
:person="resource.resource"
|
||||
:options="{ addInfo : true, addId : false, addEntity: true, addLink: false, addAltNames: true, addAge : false, hLevel : 3 }"
|
||||
@@ -17,7 +17,7 @@
|
||||
<third-party-render-box
|
||||
v-if="resource.resource.type === 'thirdparty'"
|
||||
:thirdparty="resource.resource"
|
||||
:options="{ addLink : false, addId : false, addEntity: true, addInfo: true, hLevel: 3 }"
|
||||
:options="{ addLink : false, addId : false, addEntity: true, addInfo: false, hLevel: 3 }"
|
||||
>
|
||||
<template v-slot:record-actions>
|
||||
<ul class="record_actions">
|
||||
|
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a id="section-60"></a>{{ $t('scopes.title') }}</h2>
|
||||
|
||||
<ul>
|
||||
<li v-for="s in scopes">
|
||||
<input type="checkbox" v-model="checkedScopes" :value="s" />
|
||||
{{ s.name.fr }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="!isScopeValid" class="alert alert-warning separator">
|
||||
{{ $t('scopes.add_at_least_one') }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: "Scopes",
|
||||
computed: {
|
||||
...mapState([
|
||||
'scopes',
|
||||
'scopesAtStart'
|
||||
]),
|
||||
...mapGetters([
|
||||
'isScopeValid'
|
||||
]),
|
||||
checkedScopes: {
|
||||
get: function() {
|
||||
return this.$store.state.accompanyingCourse.scopes;
|
||||
},
|
||||
set: function(v) {
|
||||
this.$store.dispatch('setScopes', v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a name="section-50"></a>{{ $t('social_issue.title') }}</h2>
|
||||
<h2><a id="section-50"></a>{{ $t('social_issue.title') }}</h2>
|
||||
|
||||
<div class="my-4">
|
||||
<!--label for="field">{{ $t('social_issue.label') }}</label
|
||||
@@ -78,6 +78,7 @@ export default {
|
||||
@import 'ChillMainAssets/module/bootstrap/shared';
|
||||
@import 'ChillPersonAssets/chill/scss/mixins';
|
||||
|
||||
div#accompanying-course {
|
||||
span.multiselect__tag {
|
||||
@include badge_social_issue;
|
||||
background: $chill-l-gray;
|
||||
@@ -93,6 +94,7 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
@@ -62,42 +62,42 @@ export default {
|
||||
// load datas DOM when mounted ready
|
||||
this.container = document.querySelector("#content");
|
||||
this.stickyNav = document.querySelector('#navmap');
|
||||
this.anchors = document.querySelectorAll("h2 a[name^='section']");
|
||||
this.initItemsMap();
|
||||
|
||||
this.anchors = document.querySelectorAll("h2 a[id^='section']");
|
||||
this.initItemsMap();
|
||||
|
||||
// TODO resizeObserver not supports IE !
|
||||
// Listen when elements change size, then recalculate heightSum and initItemsMap
|
||||
const resizeObserver = new ResizeObserver(entries => {
|
||||
this.refreshPos();
|
||||
});
|
||||
|
||||
|
||||
resizeObserver.observe(this.header);
|
||||
resizeObserver.observe(this.bannerName);
|
||||
resizeObserver.observe(this.bannerDetails);
|
||||
resizeObserver.observe(this.container);
|
||||
},
|
||||
initItemsMap() {
|
||||
|
||||
|
||||
this.anchors.forEach(anchor => {
|
||||
this.items.push({
|
||||
pos: null,
|
||||
active: false,
|
||||
key: parseInt(anchor.name.slice(8).slice(0, -1)),
|
||||
name: '#' + anchor.name
|
||||
key: parseInt(anchor.id.slice(8).slice(0, -1)),
|
||||
id: '#' + anchor.id
|
||||
})
|
||||
});
|
||||
},
|
||||
refreshPos() {
|
||||
|
||||
|
||||
//console.log('refreshPos');
|
||||
this.heightSum = this.header.offsetHeight + this.bannerName.offsetHeight + this.bannerDetails.offsetHeight;
|
||||
|
||||
|
||||
this.anchors.forEach((anchor, i) => {
|
||||
this.items[i].pos = this.findPos(anchor)['y'];
|
||||
});
|
||||
},
|
||||
findPos(element) {
|
||||
|
||||
|
||||
let posX = 0, posY = 0;
|
||||
do {
|
||||
posX += element.offsetLeft;
|
||||
@@ -105,38 +105,38 @@ export default {
|
||||
element = element.offsetParent;
|
||||
}
|
||||
while( element != null );
|
||||
|
||||
|
||||
let pos = [];
|
||||
pos['x'] = posX;
|
||||
pos['y'] = posY;
|
||||
|
||||
|
||||
return pos;
|
||||
},
|
||||
handleScroll(event) {
|
||||
|
||||
|
||||
let pos = this.findPos(this.stickyNav);
|
||||
let top = this.heightSum + this.top - window.scrollY;
|
||||
//console.log(window.scrollY);
|
||||
|
||||
if (top > this.limit) {
|
||||
//console.log(window.scrollY);
|
||||
|
||||
if (top > this.limit) {
|
||||
this.stickyNav.style.position = 'absolute';
|
||||
this.stickyNav.style.left = '10px';
|
||||
} else {
|
||||
this.stickyNav.style.position = 'fixed';
|
||||
this.stickyNav.style.left = pos['x'] + 'px';
|
||||
}
|
||||
|
||||
|
||||
this.switchActive();
|
||||
},
|
||||
switchActive() {
|
||||
|
||||
|
||||
this.items.forEach((item, i) => {
|
||||
let next = (this.items[i+1]) ? this.items[i+1].pos : '100000';
|
||||
item.active =
|
||||
item.active =
|
||||
(window.scrollY >= item.pos & window.scrollY < next) ? true : false;
|
||||
}, this);
|
||||
|
||||
// last item never switch active because scroll reach bottom of page
|
||||
|
||||
// last item never switch active because scroll reach bottom of page
|
||||
if (document.body.scrollHeight == window.scrollY + window.innerHeight) {
|
||||
this.items[this.items.length-1].active = true;
|
||||
this.items[this.items.length-2].active = false;
|
||||
@@ -151,7 +151,7 @@ export default {
|
||||
<style lang="scss">
|
||||
div#content {
|
||||
position: relative;
|
||||
|
||||
|
||||
div#navmap {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
@@ -170,7 +170,7 @@ div#content {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
&:hover,
|
||||
&:hover,
|
||||
&.active {
|
||||
span {
|
||||
display: inline;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a
|
||||
v-if="item.key <= 7"
|
||||
:href="item.name"
|
||||
v-if="item.key <= 8"
|
||||
:href="item.id"
|
||||
:class="{ 'active': isActive }"
|
||||
>
|
||||
<i class="fa fa-fw fa-square"></i>
|
||||
@@ -9,7 +9,7 @@
|
||||
</a>
|
||||
<a
|
||||
v-else-if="step === 'DRAFT'"
|
||||
:href="item.name"
|
||||
:href="item.id"
|
||||
:class="{ 'active': isActive }"
|
||||
>
|
||||
<i class="fa fa-fw fa-square"></i>
|
||||
|
@@ -42,6 +42,14 @@ const appMessages = {
|
||||
enddate: "Date de sortie",
|
||||
add_persons: "Ajouter des usagers",
|
||||
date_start_to_end: "Participation du {start} au {end}",
|
||||
leave_course: "L'usager quitte le parcours",
|
||||
sure: "Êtes-vous sûr ?",
|
||||
sure_description: "Une fois confirmé, il ne sera pas possible de faire marche arrière ! La sortie reste cependant consignée dans l'historique du parcours.",
|
||||
ok: "Oui, l'usager quitte le parcours",
|
||||
show_household_number: "Voir le ménage (n° {id})",
|
||||
show_household: "Voir le ménage",
|
||||
person_without_household_warning: "Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur appartenance à un ménage dès que possible.",
|
||||
update_household: "Modifier l'appartenance",
|
||||
},
|
||||
requestor: {
|
||||
title: "Demandeur",
|
||||
@@ -78,6 +86,10 @@ const appMessages = {
|
||||
person_locator: "Parcours localisé auprès de {0}",
|
||||
no_address: "Il n'y a pas d'adresse associée au parcours"
|
||||
},
|
||||
scopes: {
|
||||
title: "Services",
|
||||
add_at_least_one: "Indiquez au moins un service",
|
||||
},
|
||||
referrer: {
|
||||
title: "Référent du parcours",
|
||||
label: "Vous pouvez choisir un TMS ou vous assigner directement comme référent",
|
||||
@@ -105,6 +117,7 @@ const appMessages = {
|
||||
participation_not_valid: "sélectionnez au minimum 1 usager",
|
||||
socialIssue_not_valid: "sélectionnez au minimum une problématique sociale",
|
||||
location_not_valid: "indiquez au minimum une localisation temporaire du parcours",
|
||||
set_a_scope: "indiquez au moins un service",
|
||||
sure: "Êtes-vous sûr ?",
|
||||
sure_description: "Une fois le changement confirmé, il ne sera plus possible de le remettre à l'état de brouillon !",
|
||||
ok: "Confirmer le parcours"
|
||||
|
@@ -1,28 +1,41 @@
|
||||
import 'es6-promise/auto';
|
||||
import { createStore } from 'vuex';
|
||||
import { fetchScopes } from 'ChillMainAssets/lib/api/scope.js';
|
||||
import { getAccompanyingCourse,
|
||||
patchAccompanyingCourse,
|
||||
confirmAccompanyingCourse,
|
||||
postParticipation,
|
||||
postRequestor,
|
||||
postResource,
|
||||
postSocialIssue } from '../api';
|
||||
postSocialIssue,
|
||||
addScope,
|
||||
removeScope,
|
||||
} from '../api';
|
||||
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production';
|
||||
const id = window.accompanyingCourseId;
|
||||
|
||||
let initPromise = getAccompanyingCourse(id)
|
||||
.then(accompanying_course => new Promise((resolve, reject) => {
|
||||
let scopesPromise = fetchScopes();
|
||||
let accompanyingCoursePromise = getAccompanyingCourse(id);
|
||||
|
||||
let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
|
||||
.then(([scopes, accompanyingCourse]) => new Promise((resolve, reject) => {
|
||||
|
||||
const store = createStore({
|
||||
strict: debug,
|
||||
modules: {
|
||||
},
|
||||
state: {
|
||||
accompanyingCourse: accompanying_course,
|
||||
accompanyingCourse: accompanyingCourse,
|
||||
addressContext: {},
|
||||
errorMsg: []
|
||||
errorMsg: [],
|
||||
// all the available scopes
|
||||
scopes: scopes,
|
||||
// the scopes at start. If the user remove all scopes, we re-add those scopes, by security
|
||||
scopesAtStart: accompanyingCourse.scopes.map(scope => scope),
|
||||
// the scope states at server side
|
||||
scopesAtBackend: accompanyingCourse.scopes.map(scope => scope),
|
||||
},
|
||||
getters: {
|
||||
isParticipationValid(state) {
|
||||
@@ -34,11 +47,16 @@ let initPromise = getAccompanyingCourse(id)
|
||||
isLocationValid(state) {
|
||||
return state.accompanyingCourse.location !== null;
|
||||
},
|
||||
isScopeValid(state) {
|
||||
console.log('is scope valid', state.accompanyingCourse.scopes.length > 0);
|
||||
return state.accompanyingCourse.scopes.length > 0;
|
||||
},
|
||||
validationKeys(state, getters) {
|
||||
let keys = [];
|
||||
if (!getters.isParticipationValid) { keys.push('participation'); }
|
||||
if (!getters.isLocationValid) { keys.push('location'); }
|
||||
if (!getters.isSocialIssueValid) { keys.push('socialIssue'); }
|
||||
if (!getters.isScopeValid) { keys.push('scopes'); }
|
||||
//console.log('getter keys', keys);
|
||||
return keys;
|
||||
},
|
||||
@@ -129,14 +147,34 @@ let initPromise = getAccompanyingCourse(id)
|
||||
state.addressContext = context;
|
||||
},
|
||||
updateLocation(state, r) {
|
||||
//console.log('### mutation: set location attributes', r);
|
||||
state.accompanyingCourse.location = r.location;
|
||||
//console.log('### mutation: set location attributes', r);
|
||||
state.accompanyingCourse.locationStatus = r.locationStatus;
|
||||
if (r.locationStatus !== 'person') {
|
||||
state.addressContext.addressId = r.location.address_id;
|
||||
//console.log('mutation: update context addressId', state.addressContext.addressId);
|
||||
}
|
||||
state.accompanyingCourse.location = r.location;
|
||||
state.accompanyingCourse.personLocation = r.personLocation;
|
||||
},
|
||||
setEditContextTrue(state) {
|
||||
//console.log('### mutation: set edit context = true');
|
||||
setEditContextTrue(state, payload) {
|
||||
//console.log('### mutation: set edit context true with addressId', payload.addressId);
|
||||
state.addressContext.edit = true;
|
||||
state.addressContext.addressId = payload.addressId;
|
||||
},
|
||||
setScopes(state, scopes) {
|
||||
state.accompanyingCourse.scopes = scopes;
|
||||
},
|
||||
addScopeAtBackend(state, scope) {
|
||||
let scopeIds = state.scopesAtBackend.map(s => s.id);
|
||||
if (!scopeIds.includes(scope.id)) {
|
||||
state.scopesAtBackend.push(scope);
|
||||
}
|
||||
},
|
||||
removeScopeAtBackend(state, scope){
|
||||
let scopeIds = state.scopesAtBackend.map(s => s.id);
|
||||
if (scopeIds.includes(scope.id)) {
|
||||
state.scopesAtBackend = state.scopesAtBackend.filter(s => s.id !== scope.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -223,6 +261,107 @@ let initPromise = getAccompanyingCourse(id)
|
||||
resolve();
|
||||
})).catch((error) => { commit('catchError', error) });
|
||||
},
|
||||
/**
|
||||
* Handle the checked/unchecked scopes
|
||||
*
|
||||
* When the user set the scopes in a invalid situation (when no scopes are cheched), this
|
||||
* method will internally re-add the scopes as they were originally when the page was loaded, but
|
||||
* this does not appears for the user (they remains unchecked). When the user re-add a scope, the
|
||||
* scope is back in a valid state, and the store synchronize with the new state (all the original scopes
|
||||
* are removed if necessary, and the new checked scopes is backed).
|
||||
*
|
||||
* So, for instance:
|
||||
*
|
||||
* at load:
|
||||
*
|
||||
* [x] scope A (at backend: [x])
|
||||
* [x] scope B (at backend: [x])
|
||||
* [ ] scope C (at backend: [ ])
|
||||
*
|
||||
* The user uncheck scope A:
|
||||
*
|
||||
* [ ] scope A (at backend: [ ] as soon as the operation finish)
|
||||
* [x] scope B (at backend: [x])
|
||||
* [ ] scope C (at backend: [ ])
|
||||
*
|
||||
* The user uncheck scope B. The state is invalid (no scope checked), so we go back to initial state when
|
||||
* the page loaded):
|
||||
*
|
||||
* [ ] scope A (at backend: [x] as soon as the operation finish)
|
||||
* [ ] scope B (at backend: [x] as soon as the operation finish)
|
||||
* [ ] scope C (at backend: [ ])
|
||||
*
|
||||
* The user check scope C. The scopes are back to valid state. So we go back to synchronization with UI and
|
||||
* backend):
|
||||
*
|
||||
* [ ] scope A (at backend: [ ] as soon as the operation finish)
|
||||
* [ ] scope B (at backend: [ ] as soon as the operation finish)
|
||||
* [x] scope C (at backend: [x] as soon as the operation finish)
|
||||
*
|
||||
* **Warning** There is a problem if the user check/uncheck faster than the backend is synchronized.
|
||||
*
|
||||
* @param commit
|
||||
* @param state
|
||||
* @param dispatch
|
||||
* @param scopes
|
||||
* @returns Promise
|
||||
*/
|
||||
setScopes({ commit, state, dispatch }, scopes) {
|
||||
let currentServerScopesIds = state.scopesAtBackend.map(scope => scope.id);
|
||||
let checkedScopesIds = scopes.map(scope => scope.id);
|
||||
let removedScopesIds = currentServerScopesIds.filter(id => !checkedScopesIds.includes(id));
|
||||
let addedScopesIds = checkedScopesIds.filter(id => !currentServerScopesIds.includes(id));
|
||||
let lengthAfterOperation = currentServerScopesIds.length + addedScopesIds.length
|
||||
- removedScopesIds.length;
|
||||
|
||||
if (lengthAfterOperation > 0 || (lengthAfterOperation === 0 && state.scopesAtStart.length === 0) ) {
|
||||
return dispatch('updateScopes', {
|
||||
addedScopesIds, removedScopesIds
|
||||
}).then(() => {
|
||||
// warning: when the operation of dispatch are too slow, the user may check / uncheck before
|
||||
// the end of the synchronisation with the server (done by dispatch operation). Then, it leads to
|
||||
// check/uncheck in the UI. I do not know of to avoid it.
|
||||
commit('setScopes', scopes);
|
||||
return Promise.resolve();
|
||||
});
|
||||
} else {
|
||||
return dispatch('setScopes', state.scopesAtStart).then(() => {
|
||||
commit('setScopes', scopes);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Internal function for the store to effectively update scopes.
|
||||
*
|
||||
* Return a promise which resolves when all update operation are
|
||||
* successful and finished.
|
||||
*
|
||||
* @param state
|
||||
* @param commit
|
||||
* @param addedScopesIds
|
||||
* @param removedScopesIds
|
||||
* @return Promise
|
||||
*/
|
||||
updateScopes({ state, commit }, { addedScopesIds, removedScopesIds }) {
|
||||
let promises = [];
|
||||
state.scopes.forEach(scope => {
|
||||
if (addedScopesIds.includes(scope.id)) {
|
||||
promises.push(addScope(state.accompanyingCourse.id, scope).then(() => {
|
||||
commit('addScopeAtBackend', scope);
|
||||
return Promise.resolve();
|
||||
}));
|
||||
}
|
||||
if (removedScopesIds.includes(scope.id)) {
|
||||
promises.push(removeScope(state.accompanyingCourse.id, scope).then(() => {
|
||||
commit('removeScopeAtBackend', scope);
|
||||
return Promise.resolve();
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
},
|
||||
postFirstComment({ commit }, payload) {
|
||||
//console.log('## action: postFirstComment: payload', payload);
|
||||
patchAccompanyingCourse(id, { type: "accompanying_period", initialComment: payload })
|
||||
@@ -260,8 +399,8 @@ let initPromise = getAccompanyingCourse(id)
|
||||
})).catch((error) => { commit('catchError', error) });
|
||||
},
|
||||
updateLocation({ commit }, payload) {
|
||||
//console.log('## action: updateLocation', payload.locationStatusTo);
|
||||
let body = { 'type': payload.entity, 'id': payload.entityId };
|
||||
//console.log('## action: updateLocation', payload.locationStatusTo);
|
||||
let body = { 'type': payload.target, 'id': payload.targetId };
|
||||
let location = {};
|
||||
if (payload.locationStatusTo === 'person') { // patch for person address (don't remove addressLocation)
|
||||
location = { 'personLocation': { 'type': 'person', 'id': payload.personId }};
|
||||
@@ -273,7 +412,7 @@ let initPromise = getAccompanyingCourse(id)
|
||||
location = { 'personLocation': null };
|
||||
}
|
||||
Object.assign(body, location);
|
||||
patchAccompanyingCourse(payload.entityId, body)
|
||||
patchAccompanyingCourse(payload.targetId, body)
|
||||
.then(accompanyingCourse => new Promise((resolve, reject) => {
|
||||
commit('updateLocation', {
|
||||
location: accompanyingCourse.location,
|
||||
|
@@ -35,7 +35,7 @@
|
||||
<ul>
|
||||
<li v-for="p in personsReachables" :key="p.id">
|
||||
<input type="checkbox" :value="p.id" v-model="personsPicked">
|
||||
<person :person="p"></person>
|
||||
<person-render-box render="badge" :options="{}" :person="p"></person-render-box>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -124,7 +124,7 @@
|
||||
import { mapState, mapActions, mapGetters } from 'vuex';
|
||||
import VueMultiselect from 'vue-multiselect';
|
||||
import { dateToISO, ISOToDate } from 'ChillMainAssets/chill/js/date.js';
|
||||
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
|
||||
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
@@ -145,7 +145,7 @@ export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
VueMultiselect,
|
||||
Person,
|
||||
PersonRenderBox,
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
|
@@ -122,7 +122,7 @@
|
||||
<ul>
|
||||
<li v-for="p in personsReachables" :key="p.id">
|
||||
<input v-model="personsPicked" :value="p.id" type="checkbox">
|
||||
<person :person="p"></person>
|
||||
<person-render-box render="badge" :options="{}" :person="p"></person-render-box>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ handlingThirdParty.text }}</p>
|
||||
<show-address :address="handlingThirdParty.address"></show-address>
|
||||
<address-render-box :address="handlingThirdParty.address"></address-render-box>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
@@ -173,7 +173,7 @@
|
||||
<ul>
|
||||
<li v-for="t in thirdParties">
|
||||
<p>{{ t.text }}</p>
|
||||
<show-address :address="t.address"></show-address>
|
||||
<address-render-box :address="t.address"></address-render-box>
|
||||
|
||||
<ul class="record_actions">
|
||||
<button :title="$t('remove_thirdparty')" class="btn btn-remove"
|
||||
@@ -229,9 +229,9 @@ import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
|
||||
import AddResult from './components/AddResult.vue';
|
||||
import AddEvaluation from './components/AddEvaluation.vue';
|
||||
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
|
||||
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
||||
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
|
||||
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
|
||||
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
@@ -274,8 +274,8 @@ export default {
|
||||
AddResult,
|
||||
AddEvaluation,
|
||||
AddPersons,
|
||||
Person,
|
||||
ShowAddress,
|
||||
PersonRenderBox,
|
||||
AddressRenderBox,
|
||||
},
|
||||
i18n,
|
||||
data() {
|
||||
|
@@ -1,21 +0,0 @@
|
||||
import { addressMessages } from 'ChillMainAssets/vuejs/Address/i18n'
|
||||
|
||||
const appMessages = {
|
||||
fr: {
|
||||
select_a_existing_address: 'Sélectionner une adresse existante',
|
||||
create_a_new_address: 'Créer une nouvelle adresse',
|
||||
add_an_address_to_household: 'Enregistrer',
|
||||
validFrom: 'Date du déménagement',
|
||||
move_date: 'Date du déménagement',
|
||||
back_to_the_list: 'Retour à la liste',
|
||||
household_address_move_success: 'La nouvelle adresse du ménage est enregistrée',
|
||||
household_address_edit_success: 'L\'adresse du ménage a été mise à jour',
|
||||
loading: 'chargement en cours...'
|
||||
}
|
||||
};
|
||||
|
||||
Object.assign(appMessages.fr, addressMessages.fr);
|
||||
|
||||
export {
|
||||
appMessages
|
||||
};
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<h2>{{ $t('household_members_editor.concerned.title') }}</h2>
|
||||
<h2 class="mt-4">{{ $t('household_members_editor.concerned.title') }}</h2>
|
||||
|
||||
<h3 v-if="needsPositionning">
|
||||
{{ $t('household_members_editor.concerned.persons_to_positionnate') }}
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<div>
|
||||
<person :person="conc.person"></person>
|
||||
<person-render-box render="badge" :options="{}" :person="conc.person"></person-render-box>
|
||||
</div>
|
||||
<div v-if="conc.person.birthdate !== null">
|
||||
{{ $t('person.born', {'gender': conc.person.gender} ) }}
|
||||
@@ -126,7 +126,7 @@ div.person {
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
|
||||
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
|
||||
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
||||
import MemberDetails from './MemberDetails.vue';
|
||||
import { ISOToDatetime } from 'ChillMainAssets/chill/js/date.js';
|
||||
|
||||
@@ -135,7 +135,7 @@ export default {
|
||||
components: {
|
||||
AddPersons,
|
||||
MemberDetails,
|
||||
Person,
|
||||
PersonRenderBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
|
@@ -1,24 +1,71 @@
|
||||
<template>
|
||||
<h2>{{ $t('household_members_editor.household_part') }}</h2>
|
||||
|
||||
<h2 class="mt-4">{{ $t('household_members_editor.household_part') }}</h2>
|
||||
|
||||
<div v-if="hasHousehold">
|
||||
<div>
|
||||
<household-viewer :household="household"></household-viewer>
|
||||
|
||||
<div class="flex-table">
|
||||
<div class="item-bloc">
|
||||
<household-render-box :household="household" :isAddressMultiline="true"></household-render-box>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isHouseholdNew && !hasHouseholdAddress">
|
||||
|
||||
<h3 >À quelle adresse habite ce ménage ?</h3>
|
||||
|
||||
<div v-if="filterAddressesSuggestion.length > 0" class="flex-table householdAddressSuggestionList">
|
||||
<div v-for="a in filterAddressesSuggestion" class="item-bloc">
|
||||
<show-address :address="a"></show-address>
|
||||
<button class="btn btn-action" @click="setHouseholdAddress(a)">
|
||||
Le ménage habite cette adresse
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="chill-no-data-statement">Aucune adresse à suggérer</span>
|
||||
<div v-if="hasAddressSuggestion" class="householdAddressSuggestion my-5">
|
||||
<h4 class="mb-3">
|
||||
{{ $t('household_members_editor.household.where_live_the_household') }}
|
||||
</h4>
|
||||
<div class="accordion" id="addressSuggestions">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading_address_suggestions">
|
||||
<button v-if="!showAddressSuggestion"
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
aria-expanded="false"
|
||||
@click="toggleAddressSuggestion">
|
||||
{{ $tc('household_members_editor.show_household_suggestion', countAddressSuggestion) }}
|
||||
</button>
|
||||
<button v-if="showAddressSuggestion"
|
||||
class="accordion-button"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
aria-expanded="true"
|
||||
@click="toggleAddressSuggestion">
|
||||
{{ $t('household_members_editor.hide_household_suggestion') }}
|
||||
</button>
|
||||
</h2>
|
||||
<div class="accordion-collapse" id="collapse_address_suggestions"
|
||||
aria-labelledby="heading_address_suggestions" data-bs-parent="#addressSuggestions">
|
||||
<div v-if="showAddressSuggestion">
|
||||
<div class="flex-table householdAddressSuggestionList">
|
||||
<div v-for="a in filterAddressesSuggestion" class="item-bloc">
|
||||
<div class="float-button bottom">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button class="btn btn-sm btn-choose" @click="setHouseholdAddress(a)">
|
||||
{{ $t('household_members_editor.household.household_live_to_this_address') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="list-content fa-ul">
|
||||
<li>
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
<address-render-box :address="a"></address-render-box>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
@@ -28,49 +75,30 @@
|
||||
:key="addAddress.key"
|
||||
:options="addAddress.options"
|
||||
:result="addAddress.result"
|
||||
@submitAddress="setHouseholdCreatedAddress"
|
||||
@addressChangedCallback="setHouseholdCreatedAddress"
|
||||
ref="addAddress">
|
||||
</add-address>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="isHouseholdNew && hasHouseholdAddress">
|
||||
<ul class="record_actions">
|
||||
<li >
|
||||
<button class="btn btn-misc" @click="removeHouseholdAddress">
|
||||
Supprimer cette adresse
|
||||
{{ $t('household_members_editor.household.delete_this_address') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-else-if="isForceLeaveWithoutHousehold">
|
||||
{{ $t('household_members_editor.household.will_leave_any_household') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="alert alert-info">{{ $t('household_members_editor.household.no_household_choose_one') }}</div>
|
||||
</div>
|
||||
<div v-else class="alert alert-info">{{ $t('household_members_editor.household.no_household_choose_one') }}</div>
|
||||
|
||||
<ul v-if="allowChangeHousehold" class="record_actions">
|
||||
<li v-if="!showHouseholdSuggestion">
|
||||
<button
|
||||
class="btn btn-misc"
|
||||
@click="toggleHouseholdSuggestion"
|
||||
>
|
||||
{{ $tc('household_members_editor.show_household_suggestion',
|
||||
countHouseholdSuggestion) }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="showHouseholdSuggestion && hasHouseholdSuggestion">
|
||||
<button
|
||||
class="btn btn-misc"
|
||||
@click="toggleHouseholdSuggestion"
|
||||
>
|
||||
{{ $t('household_members_editor.hide_household_suggestion') }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="allowHouseholdCreate">
|
||||
<button class="btn btn-create" @click="createHousehold">
|
||||
{{ $t('household_members_editor.household.create_household') }}
|
||||
@@ -93,85 +121,73 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="householdSuggestions">
|
||||
<div v-if="showHouseholdSuggestion && hasHouseholdSuggestion">
|
||||
<p>{{ $t('household_members_editor.household_for_participants_accompanying_period') }}:</p>
|
||||
<div class="householdSuggestionList">
|
||||
<div
|
||||
v-for="h in filterHouseholdSuggestionByAccompanyingPeriod"
|
||||
class="item"
|
||||
>
|
||||
<household-viewer :household="h"></household-viewer>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button class="btn btn-misc" @click="selectHousehold(h)">
|
||||
{{ $t('household_members_editor.select_household') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div >
|
||||
<div v-if="hasHouseholdSuggestion" class="householdSuggestions my-5">
|
||||
<h4 class="mb-3">
|
||||
{{ $t('household_members_editor.household_for_participants_accompanying_period') }} :
|
||||
</h4>
|
||||
<div class="accordion" id="householdSuggestions">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading_household_suggestions">
|
||||
<button v-if="!showHouseholdSuggestion"
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
aria-expanded="false"
|
||||
@click="toggleHouseholdSuggestion">
|
||||
{{ $tc('household_members_editor.show_household_suggestion', countHouseholdSuggestion) }}
|
||||
</button>
|
||||
<button v-if="showHouseholdSuggestion"
|
||||
class="accordion-button"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
aria-expanded="true"
|
||||
@click="toggleHouseholdSuggestion">
|
||||
{{ $t('household_members_editor.hide_household_suggestion') }}
|
||||
</button>
|
||||
<!-- disabled bootstrap behaviour: data-bs-target="#collapse_household_suggestions" aria-controls="collapse_household_suggestions" -->
|
||||
</h2>
|
||||
<div class="accordion-collapse" id="collapse_household_suggestions"
|
||||
aria-labelledby="heading_household_suggestions" data-bs-parent="#householdSuggestions">
|
||||
<div v-if="showHouseholdSuggestion">
|
||||
<div class="flex-table householdSuggestionList">
|
||||
<div v-for="h in filterHouseholdSuggestionByAccompanyingPeriod" class="item-bloc">
|
||||
<household-render-box :household="h"></household-render-box>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button class="btn btn-sm btn-choose" @click="selectHousehold(h)">
|
||||
{{ $t('household_members_editor.select_household') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
div.householdAddressSuggestionList {
|
||||
/*
|
||||
display: flex;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
|
||||
*/
|
||||
|
||||
& > li {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.householdSuggestionList {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
& > .item {
|
||||
margin-bottom: 0.8rem;
|
||||
width: calc(50% - 1rem);
|
||||
border: 1px solid var(--chill-light-gray);
|
||||
padding: 0.5rem 0.5rem 0 0.5rem;
|
||||
|
||||
ul.record_actions {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue';
|
||||
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
|
||||
import HouseholdRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/HouseholdRenderBox.vue';
|
||||
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||
import AddAddress from 'ChillMainAssets/vuejs/Address/components/AddAddress.vue';
|
||||
|
||||
export default {
|
||||
name: 'Household',
|
||||
components: {
|
||||
HouseholdViewer,
|
||||
ShowAddress,
|
||||
HouseholdRenderBox,
|
||||
AddressRenderBox,
|
||||
AddAddress,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addAddress: {
|
||||
context: {
|
||||
entity: {
|
||||
type: 'household_create',
|
||||
target: {
|
||||
name: 'household_create',
|
||||
id: 0
|
||||
},
|
||||
edit: false,
|
||||
@@ -179,18 +195,17 @@ export default {
|
||||
},
|
||||
key: 'household_new',
|
||||
options: {
|
||||
hideDateFrom: true,
|
||||
bindModal: {
|
||||
|
||||
useDate: {
|
||||
validFrom: true
|
||||
},
|
||||
button: {
|
||||
text: {
|
||||
create: null,
|
||||
create: 'household_members_editor.household.or_create_new_address',
|
||||
edit: null,
|
||||
}
|
||||
},
|
||||
title: {
|
||||
create: null,
|
||||
create: 'household_members_editor.household.create_new_address',
|
||||
edit: null,
|
||||
},
|
||||
}
|
||||
@@ -204,11 +219,14 @@ export default {
|
||||
'hasHouseholdSuggestion',
|
||||
'countHouseholdSuggestion',
|
||||
'filterHouseholdSuggestionByAccompanyingPeriod',
|
||||
'hasAddressSuggestion',
|
||||
'countAddressSuggestion',
|
||||
'filterAddressesSuggestion',
|
||||
'hasHouseholdAddress',
|
||||
]),
|
||||
...mapState([
|
||||
'showHouseholdSuggestion',
|
||||
'showAddressSuggestion'
|
||||
]),
|
||||
household() {
|
||||
return this.$store.state.household;
|
||||
@@ -249,8 +267,12 @@ export default {
|
||||
toggleHouseholdSuggestion() {
|
||||
this.$store.commit('toggleHouseholdSuggestion');
|
||||
},
|
||||
toggleAddressSuggestion() {
|
||||
this.$store.commit('toggleAddressSuggestion');
|
||||
},
|
||||
selectHousehold(h) {
|
||||
this.$store.dispatch('selectHousehold', h);
|
||||
this.toggleHouseholdSuggestion();
|
||||
},
|
||||
removeHousehold() {
|
||||
this.$store.dispatch('removeHousehold');
|
||||
@@ -260,15 +282,56 @@ export default {
|
||||
console.log('setHouseholdAddress', a);
|
||||
this.$store.commit('setHouseholdAddress', a);
|
||||
},
|
||||
setHouseholdCreatedAddress() {
|
||||
let payload = this.$refs.addAddress.submitNewAddress();
|
||||
setHouseholdCreatedAddress(payload) {
|
||||
console.log('setHouseholdAddress', payload);
|
||||
this.$store.dispatch('setHouseholdNewAddress', payload);
|
||||
},
|
||||
removeHouseholdAddress() {
|
||||
this.$store.commit('removeHouseholdAddress');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
div#household_members_editor div,
|
||||
div.householdSuggestionList {
|
||||
&.flex-table {
|
||||
margin: 0;
|
||||
div.item-bloc div.item-row div.item-col {
|
||||
&:first-child {
|
||||
width: 25%;
|
||||
}
|
||||
&:last-child {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
div.householdAddressSuggestionList {
|
||||
display: flex;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
& > li {}
|
||||
}
|
||||
.householdSuggestionList {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
& > .item {
|
||||
margin-bottom: 0.8rem;
|
||||
width: calc(50% - 1rem);
|
||||
border: 1px solid var(--chill-light-gray);
|
||||
padding: 0.5rem 0.5rem 0 0.5rem;
|
||||
ul.record_actions {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
</style>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<div>
|
||||
<person :person="conc.person"></person>
|
||||
<person-render-box render="badge" :options="{}" :person="conc.person"></person-render-box>
|
||||
<span v-if="isHolder" class="badge bg-primary holder">
|
||||
{{ $t('household_members_editor.holder') }}
|
||||
</span>
|
||||
@@ -73,14 +73,14 @@ div.participation-details {
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
|
||||
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
|
||||
|
||||
export default {
|
||||
name: 'MemberDetails',
|
||||
components: {
|
||||
Person,
|
||||
PersonRenderBox,
|
||||
ckeditor: CKEditor.component,
|
||||
},
|
||||
props: [
|
||||
|
@@ -7,10 +7,16 @@ const appMessages = {
|
||||
household: {
|
||||
no_household_choose_one: "Aucun ménage de destination. Choisissez un ménage. Les usagers concernés par la modification apparaitront ensuite.",
|
||||
new_household: "Nouveau ménage",
|
||||
create_household: "Créer un ménage",
|
||||
create_household: "Créer un nouveau ménage de destination",
|
||||
search_household: "Chercher un ménage",
|
||||
will_leave_any_household: "Ne rejoignent pas de ménage",
|
||||
leave_without_household: "Sans nouveau ménage"
|
||||
leave_without_household: "Sans nouveau ménage",
|
||||
where_live_the_household: "À quelle adresse habite ce ménage ?",
|
||||
household_live_to_this_address: "Sélectionner l'adresse",
|
||||
no_suggestions: "Aucune adresse à suggérer",
|
||||
delete_this_address: "Supprimer cette adresse",
|
||||
create_new_address: "Créer une nouvelle adresse",
|
||||
or_create_new_address: "Ou créer une nouvelle adresse",
|
||||
},
|
||||
concerned: {
|
||||
title: "Nouveaux membres du ménage",
|
||||
@@ -29,10 +35,11 @@ const appMessages = {
|
||||
remove_position: "Retirer des {position}",
|
||||
remove_concerned: "Ne plus transférer",
|
||||
household_part: "Ménage de destination",
|
||||
suggestions: "Suggestions",
|
||||
hide_household_suggestion: "Masquer les suggestions",
|
||||
show_household_suggestion: 'Aucune suggestion | Afficher une suggestion | Afficher {count} suggestions',
|
||||
household_for_participants_accompanying_period: "Ces ménages partagent le même parcours",
|
||||
select_household: "Choisir ce ménage",
|
||||
household_for_participants_accompanying_period: "Des ménages partagent le même parcours",
|
||||
select_household: "Sélectionner le ménage",
|
||||
dates_title: "Période de validité",
|
||||
dates: {
|
||||
start_date: "Début de validité",
|
||||
|
@@ -36,6 +36,7 @@ const store = createStore({
|
||||
householdSuggestionByAccompanyingPeriod: [],
|
||||
showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1,
|
||||
addressesSuggestion: [],
|
||||
showAddressSuggestion: true,
|
||||
warnings: [],
|
||||
errors: []
|
||||
},
|
||||
@@ -73,6 +74,12 @@ const store = createStore({
|
||||
.filter(h => h.id !== state.household.id)
|
||||
;
|
||||
},
|
||||
hasAddressSuggestion(state, getters) {
|
||||
return getters.filterAddressesSuggestion.length > 0;
|
||||
},
|
||||
countAddressSuggestion(state, getters) {
|
||||
return getters.filterAddressesSuggestion.length;
|
||||
},
|
||||
filterAddressesSuggestion(state) {
|
||||
if (state.household === null) {
|
||||
return state.addressesSuggestion;
|
||||
@@ -260,6 +267,9 @@ const store = createStore({
|
||||
toggleHouseholdSuggestion(state) {
|
||||
state.showHouseholdSuggestion = !state.showHouseholdSuggestion;
|
||||
},
|
||||
toggleAddressSuggestion(state) {
|
||||
state.showAddressSuggestion = !state.showAddressSuggestion;
|
||||
},
|
||||
setWarnings(state, warnings) {
|
||||
state.warnings = warnings;
|
||||
// reset errors, which should come from servers
|
||||
@@ -327,7 +337,7 @@ const store = createStore({
|
||||
commit('forceLeaveWithoutHousehold');
|
||||
dispatch('computeWarnings');
|
||||
},
|
||||
selectHousehold({ commit }, h) {
|
||||
selectHousehold({ commit, dispatch }, h) {
|
||||
commit('selectHousehold', h);
|
||||
dispatch('computeWarnings');
|
||||
},
|
||||
|
@@ -1,48 +0,0 @@
|
||||
// CURRENTLY NOT IN USE
|
||||
|
||||
<template>
|
||||
|
||||
<li v-if="address" class="chill-entity entity-address">
|
||||
<i v-if="options.with_picto == true" class="fa fa-fw fa-map-marker"></i>
|
||||
<span v-if="options.render == 'list' || options.render == 'list'" :class="'address ' + {'multiline' : options.multiline === true}">
|
||||
|
||||
|
||||
<!-- if address.street is not empty -->
|
||||
<p v-if="address.street" class="street">{{ address.street }}
|
||||
<!-- if address.streetNumber is not empty -->
|
||||
<span v-if="address.streetNumber" class="streetnumber">{{ address.streetNumber }}</span>
|
||||
</p>
|
||||
|
||||
<!-- if options['extended_infos'] -->
|
||||
<div v-if="options.extended_infos == true">
|
||||
<span v-if="address.floor" class="floor">{{ address.floor }}</span>
|
||||
<span v-if="address.corridor" class="corridor">{{ address.corridor }}</span>
|
||||
<span v-if="address.steps" class="steps">{{ address.steps }}</span>
|
||||
<span v-if="address.buildingName" class="buildingName">{{ address.buildingName }}</span>
|
||||
<span v-if="address.flat" class="flat">{{ address.flat }}</span>
|
||||
<span v-if="address.distribution" class="distribution">{{ address.distribution }}</span>
|
||||
<span v-if="address.extra" class="extra">{{ address.extra }}</span>
|
||||
</div>
|
||||
|
||||
<!-- if address.postCode is not empty -->
|
||||
<div v-if="address.postCode">
|
||||
<p class="postcode">
|
||||
<span class="code">{{ address.postCode.code }}</span>
|
||||
<span class="name">{{ address.postCode.name }}</span>
|
||||
</p>
|
||||
<p class="country">{{ address.postCode.country.name }}</p>
|
||||
</div>
|
||||
|
||||
</span>
|
||||
</li>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default{
|
||||
name: "AddressRenderBox",
|
||||
props: ['address', 'options']
|
||||
}
|
||||
|
||||
</script>
|
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<section class="chill-entity entity-household">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
|
||||
<!-- identifier -->
|
||||
<div v-if="isHouseholdNew()" class="h4">
|
||||
<i class="fa fa-home"></i>
|
||||
{{ $t('new_household') }}
|
||||
</div>
|
||||
<div v-else class="h4">
|
||||
<i class="fa fa-home"></i>
|
||||
{{ $t('household_number', { number: household.id } ) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
|
||||
<!-- member part -->
|
||||
<li v-if="hasCurrentMembers" class="members" :title="$t('current_members')">
|
||||
<template v-for="m in currentMembers()" :key="m.id">
|
||||
<person-render-box render="badge"
|
||||
:person="m.person"
|
||||
:options="{
|
||||
isHolder: m.holder,
|
||||
addLink: true
|
||||
}">
|
||||
</person-render-box>
|
||||
</template>
|
||||
</li>
|
||||
<li v-else class="members" :title="$t('current_members')">
|
||||
<p class="chill-no-data-statement">{{ $t('no_members_yet') }}</p>
|
||||
</li>
|
||||
|
||||
<!-- address part -->
|
||||
<li v-if="hasAddress()">
|
||||
<address-render-box :address="household.current_address" :isMultiline="isMultiline"></address-render-box>
|
||||
</li>
|
||||
<li v-else>
|
||||
<span class="chill-no-data-statement">{{ $t('no_current_address') }}</span>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
||||
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||
|
||||
const i18n = {
|
||||
"messages": {
|
||||
"fr": {
|
||||
"household_number": "Ménage n°{number}",
|
||||
"current_members": "Membres actuels",
|
||||
"no_current_address": "Sans adresse actuellement",
|
||||
"new_household": "Nouveau ménage",
|
||||
"no_members_yet": "Aucun membre actuellement",
|
||||
"holder": "titulaire",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'HouseholdRenderBox',
|
||||
props: ['household', 'isAddressMultiline'],
|
||||
components: {
|
||||
PersonRenderBox,
|
||||
AddressRenderBox,
|
||||
},
|
||||
i18n,
|
||||
computed: {
|
||||
isMultiline() {
|
||||
return (typeof this.isAddressMultiline !== 'undefined') ? this.isAddressMultiline : false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hasCurrentMembers() {
|
||||
return this.household.current_members_id.length > 0;
|
||||
},
|
||||
currentMembers() {
|
||||
return this.household.members.filter(m => this.household.current_members_id.includes(m.id))
|
||||
.sort((a, b) => {
|
||||
if (a.position.ordering < b.position.ordering) {
|
||||
return -1;
|
||||
}
|
||||
if (a.position.ordering > b.position.ordering) {
|
||||
return 1;
|
||||
}
|
||||
if (a.holder && !b.holder) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.holder && b.holder) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
currentMembersLength() {
|
||||
return this.household.current_members_id.length;
|
||||
},
|
||||
isHouseholdNew() {
|
||||
return !Number.isInteger(this.household.id);
|
||||
},
|
||||
hasAddress() {
|
||||
return this.household.current_address !== null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
section.chill-entity {
|
||||
&.entity-household {
|
||||
|
||||
ul.list-content li::marker {
|
||||
content: '';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="item-bloc">
|
||||
<div v-if="render === 'bloc'" class="item-bloc">
|
||||
<section class="chill-entity entity-person">
|
||||
<div class="item-row entity-bloc">
|
||||
<div class="item-row entity-bloc">
|
||||
|
||||
<div class="item-col">
|
||||
<div class="entity-label">
|
||||
|
||||
<div :class="'denomination h' + options.hLevel">
|
||||
|
||||
<a v-if="this.options.addLink == true" href="#">
|
||||
<a v-if="options.addLink === true" :href="getUrl">
|
||||
<span class="firstname">{{ person.firstName }}</span>
|
||||
<span class="lastname">{{ person.lastName }}</span>
|
||||
<span v-if="person.altNames && options.addAltNames == true" class="altnames">
|
||||
@@ -27,80 +27,121 @@
|
||||
|
||||
</div>
|
||||
|
||||
<p v-if="this.options.addInfo == true" class="moreinfo">
|
||||
<p v-if="options.addInfo == true" class="moreinfo">
|
||||
<i :class="'fa fa-fw ' + getGenderIcon" title="{{ getGender }}"></i>
|
||||
<time v-if="person.birthdate" datetime="{{ person.birthdate }}" title="{{ birthdate }}">
|
||||
{{ $t(getGender) + ' ' + $d(birthdate, 'text') }}
|
||||
<time v-if="person.birthdate && !person.deathdate" datetime="{{ person.birthdate }}" title="{{ birthdate }}">
|
||||
{{ $t(getGenderTranslation) + ' ' + $d(birthdate, 'text') }}
|
||||
</time>
|
||||
<time v-else-if="person.deathdate" datetime="{{ person.deathdate }}" title="{{ person.deathdate }}">
|
||||
<time v-else-if="person.birthdate && person.deathdate" datetime="{{ person.deathdate }}" title="{{ person.deathdate }}">
|
||||
{{ birthdate }} - {{ deathdate }}
|
||||
</time>
|
||||
<!-- <span class="age">{{ person.age }}</span> -->
|
||||
<time v-else-if="person.deathdate" datetime="{{ person.deathdate }}" title="{{ person.deathdate }}">
|
||||
{{ $t('renderbox.deathdate') + ' ' + deathdate }}
|
||||
</time>
|
||||
<span v-if="options.addAge && person.birthdate" class="age">{{ getAge }} {{ $t('renderbox.years_old')}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
<div class="float-button bottom">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
<slot name="record-actions"></slot>
|
||||
</div>
|
||||
<ul class="list-content fa-ul">
|
||||
|
||||
<li v-if="person.current_household_address">
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
<show-address :address="person.current_household_address" :isMultiline="isMultiline"></show-address>
|
||||
</li>
|
||||
<li v-else-if="options.addNoData">
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
|
||||
</li>
|
||||
<li v-if="person.current_household_id">
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
<address-render-box v-if="person.current_household_address"
|
||||
:address="person.current_household_address"
|
||||
:isMultiline="isMultiline">
|
||||
</address-render-box>
|
||||
<p v-else class="chill-no-data-statement">
|
||||
{{ $t('renderbox.household_without_address') }}
|
||||
</p>
|
||||
<a v-if="options.addHouseholdLink === true"
|
||||
:href="getCurrentHouseholdUrl"
|
||||
:title="$t('persons_associated.show_household_number', {id: person.current_household_id})">
|
||||
<span class="badge rounded-pill bg-chill-beige">
|
||||
<i class="fa fa-fw fa-home"></i><!--{{ $t('persons_associated.show_household') }}-->
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li v-else-if="options.addNoData">
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
<p class="chill-no-data-statement">
|
||||
{{ $t('renderbox.no_data') }}
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li v-if="person.mobilenumber">
|
||||
<i class="fa fa-li fa-mobile"></i>
|
||||
<a :href="'tel: ' + person.mobilenumber">{{ person.mobilenumber }}</a>
|
||||
</li>
|
||||
<li v-else-if="options.addNoData">
|
||||
<i class="fa fa-li fa-mobile"></i>
|
||||
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
|
||||
</li>
|
||||
<li v-if="person.phonenumber">
|
||||
<i class="fa fa-li fa-phone"></i>
|
||||
<a :href="'tel: ' + person.phonenumber">{{ person.phonenumber }}</a>
|
||||
</li>
|
||||
<li v-else-if="options.addNoData">
|
||||
<i class="fa fa-li fa-phone"></i>
|
||||
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
|
||||
</li>
|
||||
<li v-if="person.mobilenumber">
|
||||
<i class="fa fa-li fa-mobile"></i>
|
||||
<a :href="'tel: ' + person.mobilenumber">{{ person.mobilenumber }}</a>
|
||||
</li>
|
||||
<li v-else-if="options.addNoData">
|
||||
<i class="fa fa-li fa-mobile"></i>
|
||||
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
|
||||
</li>
|
||||
<li v-if="person.phonenumber">
|
||||
<i class="fa fa-li fa-phone"></i>
|
||||
<a :href="'tel: ' + person.phonenumber">{{ person.phonenumber }}</a>
|
||||
</li>
|
||||
<li v-else-if="options.addNoData">
|
||||
<i class="fa fa-li fa-phone"></i>
|
||||
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
|
||||
</li>
|
||||
|
||||
<li v-if="person.center && options.addCenter">
|
||||
<i class="fa fa-li fa-long-arrow-right"></i>
|
||||
{{ person.center.name }}
|
||||
</li>
|
||||
<li v-else-if="options.addNoData">
|
||||
<i class="fa fa-li fa-long-arrow-right"></i>
|
||||
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
|
||||
</li>
|
||||
<slot name="custom-zone"></slot>
|
||||
<li v-if="person.center && options.addCenter">
|
||||
<i class="fa fa-li fa-long-arrow-right"></i>
|
||||
{{ person.center.name }}
|
||||
</li>
|
||||
<li v-else-if="options.addNoData">
|
||||
<i class="fa fa-li fa-long-arrow-right"></i>
|
||||
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
|
||||
</li>
|
||||
<slot name="custom-zone"></slot>
|
||||
|
||||
</ul>
|
||||
|
||||
<slot name="record-actions"></slot>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span v-if="render === 'badge'" class="chill-entity entity-person badge-person">
|
||||
<a v-if="options.addLink === true" :href="getUrl">
|
||||
<span v-if="options.isHolder" class="fa-stack fa-holder" :title="$t('renderbox.holder')">
|
||||
<i class="fa fa-circle fa-stack-1x text-success"></i>
|
||||
<i class="fa fa-stack-1x">T</i>
|
||||
</span>
|
||||
{{ person.text }}
|
||||
</a>
|
||||
<span v-else>
|
||||
<span v-if="options.isHolder" class="fa-stack fa-holder" :title="$t('renderbox.holder')">
|
||||
<i class="fa fa-circle fa-stack-1x text-success"></i>
|
||||
<i class="fa fa-stack-1x">T</i>
|
||||
</span>
|
||||
{{ person.text }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {dateToISO} from 'ChillMainAssets/chill/js/date.js';
|
||||
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
|
||||
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||
|
||||
export default {
|
||||
name: "PersonRenderBox",
|
||||
components: {
|
||||
ShowAddress
|
||||
AddressRenderBox
|
||||
},
|
||||
props: ['person', 'options'],
|
||||
props: ['person', 'options', 'render', 'returnPath'],
|
||||
computed: {
|
||||
getGender: function() {
|
||||
getGenderTranslation: function() {
|
||||
return this.person.gender == 'woman' ? 'renderbox.birthday.woman' : 'renderbox.birthday.man';
|
||||
},
|
||||
isMultiline: function() {
|
||||
@@ -114,23 +155,60 @@ export default {
|
||||
return this.person.gender == 'woman' ? 'fa-venus' : this.person.gender == 'man' ? 'fa-mars' : 'fa-neuter';
|
||||
},
|
||||
birthdate: function(){
|
||||
var date = new Date(this.person.birthdate.datetime);
|
||||
return dateToISO(date);
|
||||
if(this.person.birthdate !== null){
|
||||
const date = new Date(this.person.birthdate.datetime);
|
||||
return dateToISO(date)
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
deathdate: function(){
|
||||
var date = new Date(this.person.deathdate.datetime);
|
||||
return dateToISO(date);
|
||||
// TODO FIX edition conflict: if null or undefined ?
|
||||
// if (typeof this.person.deathdate !== 'undefined') {
|
||||
// var date = new Date(this.person.deathdate.datetime);
|
||||
// return dateToISO(date);
|
||||
//}
|
||||
if(this.person.deathdate !== null){
|
||||
const date = new Date(this.person.deathdate.datetime);
|
||||
return date.toLocaleDateString("fr-FR");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
altNameLabel: function(){
|
||||
altNameLabel: function() {
|
||||
for(let i = 0; i < this.person.altNames.length; i++){
|
||||
return this.person.altNames[i].label
|
||||
}
|
||||
},
|
||||
altNameKey: function(){
|
||||
altNameKey: function() {
|
||||
for(let i = 0; i < this.person.altNames.length; i++){
|
||||
return this.person.altNames[i].key
|
||||
}
|
||||
},
|
||||
getUrl: function() {
|
||||
return `/fr/person/${this.person.id}/general`;
|
||||
},
|
||||
getAge: function() {
|
||||
if(this.person.birthdate && !this.person.deathdate){
|
||||
const birthday = new Date(this.person.birthdate.datetime)
|
||||
const now = new Date()
|
||||
return (now.getFullYear() - birthday.getFullYear())
|
||||
} else if(this.person.birthdate && this.person.deathdate){
|
||||
const birthday = new Date(this.person.birthdate.datetime)
|
||||
const deathdate = new Date(this.person.deathdate.datetime)
|
||||
return (deathdate.getFullYear() - birthday.getFullYear())
|
||||
} else if(!this.person.birthdate && this.person.deathdate.datetime) {
|
||||
// todo: change this
|
||||
return "Age unknown"
|
||||
} else {
|
||||
// todo: change this
|
||||
return "Age unknown"
|
||||
}
|
||||
},
|
||||
getCurrentHouseholdUrl: function() {
|
||||
let returnPath = this.returnPath ? `?returnPath=${this.returnPath}` : ``;
|
||||
return `/fr/person/household/${this.person.current_household_id}/summary${returnPath}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -149,10 +227,17 @@ div.flex-table {
|
||||
}
|
||||
div.item-col:last-child {
|
||||
justify-content: flex-start;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.age{
|
||||
margin-left: 0.5em;
|
||||
&:before { content: '('; }
|
||||
&:after { content: ')'; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
@@ -1,139 +0,0 @@
|
||||
<template>
|
||||
<div class="chill-entity chill-entity__household">
|
||||
<!-- identifier -->
|
||||
<div v-if="isHouseholdNew()" class="identifier">
|
||||
<i class="fa fa-home"></i>
|
||||
{{ $t('new_household') }}
|
||||
</div>
|
||||
<div v-else class="identifier">
|
||||
<i class="fa fa-home"></i>
|
||||
{{ $t('household_number', { number: household.id } ) }}
|
||||
</div>
|
||||
|
||||
<!-- member part -->
|
||||
<div v-if="hasCurrentMembers" class="members">
|
||||
<span class="current-members">{{ $t('current_members') }}: </span>
|
||||
<template v-for="(m, index) in currentMembers()" :key="m.id">
|
||||
<person :person="m.person"></person>
|
||||
<span v-if="m.holder">
|
||||
<span class="badge bg-primary">{{ $t('holder') }}</span>
|
||||
</span>
|
||||
<span v-if="index != (currentMembersLength() - 1)">, </span>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="members">
|
||||
<p class="chill-no-data-statement">{{ $t('no_members_yet') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- address part -->
|
||||
<div v-if="hasAddress()" class="where">
|
||||
<i class="fa fa-where"></i>
|
||||
<show-address :address="household.current_address"></show-address>
|
||||
</div>
|
||||
<div v-else class="where">
|
||||
<i class="fa fa-where"></i>
|
||||
<p class="chill-no-data-statement">{{ $t('no_current_address') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.chill-entity__household {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"identifier identifier where"
|
||||
"who who where"
|
||||
;
|
||||
grid-template-columns:
|
||||
auto auto 30%
|
||||
;
|
||||
|
||||
.identifier {
|
||||
grid-area: identifier;
|
||||
|
||||
font-size: 1.3em;
|
||||
font-weight: 700;
|
||||
color: var(--chill-blue);
|
||||
|
||||
}
|
||||
.members {
|
||||
grid-area: who;
|
||||
|
||||
.current-members {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
.where {
|
||||
grid-area: where
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
|
||||
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
|
||||
|
||||
const i18n = {
|
||||
"messages":
|
||||
{
|
||||
"fr":
|
||||
{
|
||||
"household_number": "Ménage #{number}",
|
||||
"current_members": "Membres actuels",
|
||||
"no_current_address": "Sans adresse actuellement",
|
||||
"new_household": "Nouveau ménage",
|
||||
"no_members_yet": "Aucun membre actuellement",
|
||||
"holder": "titulaire",
|
||||
}
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
export default {
|
||||
name: 'Household',
|
||||
props: ['household'],
|
||||
components: {
|
||||
Person,
|
||||
ShowAddress,
|
||||
},
|
||||
i18n,
|
||||
methods: {
|
||||
hasCurrentMembers() {
|
||||
return this.household.current_members_id.length > 0;
|
||||
},
|
||||
currentMembers() {
|
||||
return this.household.members.filter(m => this.household.current_members_id.includes(m.id))
|
||||
.sort((a, b) => {
|
||||
if (a.position.ordering < b.position.ordering) {
|
||||
return -1;
|
||||
}
|
||||
if (a.position.ordering > b.position.ordering) {
|
||||
return 1;
|
||||
}
|
||||
if (a.holder && !b.holder) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.holder && b.holder) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
currentMembersLength() {
|
||||
return this.household.current_members_id.length;
|
||||
},
|
||||
isHouseholdNew() {
|
||||
return !Number.isInteger(this.household.id);
|
||||
},
|
||||
hasAddress() {
|
||||
return this.household.current_address !== null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div v-if="action === 'show'">
|
||||
<div class="flex-table">
|
||||
<person-render-box
|
||||
<person-render-box render="bloc"
|
||||
:person="person"
|
||||
:options="{
|
||||
addInfo: true,
|
||||
addEntity: false,
|
||||
addAltNames: true,
|
||||
addAge: true,
|
||||
addId: true,
|
||||
addLink: false,
|
||||
hLevel: 3,
|
||||
|
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<span class="chill-entity chill-entity__person">
|
||||
<span class="chill-entity__person__text chill_denomination">
|
||||
{{ person.text }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Person',
|
||||
props: ['person']
|
||||
}
|
||||
|
||||
</script>
|
@@ -1,7 +1,7 @@
|
||||
{% extends '@ChillPerson/AccompanyingCourse/layout.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Accompanying Course Details'|trans }}
|
||||
{{ 'Accompanying Course History'|trans }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -15,8 +15,8 @@
|
||||
Il faudrait peut-être modifier son adresse comme ceci: `/fr/parcours/{id}/timeline`
|
||||
</p>
|
||||
|
||||
{# start test flex-table
|
||||
|
||||
{# start test flex-table
|
||||
|
||||
<div class="flex-table">
|
||||
{% for p in accompanyingCourse.participations %}
|
||||
<div class="item-bloc">
|
||||
@@ -52,16 +52,16 @@
|
||||
<li><button type="button" class="btn btn-edit"></button></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="item-row">
|
||||
Lorem ipsum dolor sit amet, incididunt ut labore et dolore magna aliqua.
|
||||
Lorem ipsum dolor sit amet, incididunt ut labore et dolore magna aliqua.
|
||||
</div>
|
||||
<div class="item-row">
|
||||
Rhoncus est pellentesque elit eu ultrices vitae auctor.
|
||||
Rhoncus est pellentesque elit eu ultrices vitae auctor.
|
||||
</div>
|
||||
<div class="item-row">
|
||||
Facilisis gravida neque convallis a cras semper auctor neque.
|
||||
Facilisis gravida neque convallis a cras semper auctor neque.
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -70,5 +70,5 @@
|
||||
{# end test flex-table #}
|
||||
|
||||
{# ==> insert accompanyingCourse vue component #}
|
||||
<div id="accompanying-course"></div>
|
||||
<div id="accompanying-course"></div>
|
||||
{% endblock %}
|
||||
|
@@ -34,6 +34,9 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<pre>WIP .. AccompanyingCourse Resume dashboard</pre>
|
||||
|
||||
{#
|
||||
<h1>{{ 'Resume Accompanying Course'|trans }}</h1>
|
||||
|
||||
<div class="associated-persons mb-5">
|
||||
@@ -50,11 +53,13 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
#}
|
||||
{% if 'DRAFT' != accompanyingCourse.step %}
|
||||
{% if withoutHousehold|length > 0 %}
|
||||
{% include '@ChillPerson/AccompanyingCourse/_join_household.html.twig' with {} %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{#
|
||||
</div>
|
||||
|
||||
<div class="location mb-5">
|
||||
@@ -75,12 +80,11 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
#}
|
||||
{% if accompanyingCourse.locationStatus == 'address' or accompanyingCourse.locationStatus == 'none' %}
|
||||
{% include '@ChillPerson/AccompanyingCourse/_warning_address.html.twig' with {} %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{#
|
||||
</div>
|
||||
|
||||
<div class="requestor mb-5">
|
||||
@@ -128,6 +132,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
#}
|
||||
<div class="mt-5"></div>
|
||||
|
||||
<div class="social-actions mb-5">
|
||||
<h2 class="mb-3">{{ 'Social actions'|trans }}</h2>
|
||||
|
@@ -1,72 +0,0 @@
|
||||
{#
|
||||
This Twig template include load vue_address component.
|
||||
It push all variables from context in Address/App.vue.
|
||||
|
||||
OPTIONS
|
||||
* mode string ['edit*'|'new'|'create']
|
||||
* address_id integer
|
||||
* backUrl twig route: path('route', {parameters})
|
||||
* modalTitle twig translated chain
|
||||
* buttonText twig translated chain
|
||||
* buttonSize bootstrap class like 'btn-sm'
|
||||
|
||||
#}
|
||||
<div id="address"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.vueRootComponent = 'app';
|
||||
|
||||
{% if person is defined %}
|
||||
window.entityType = 'person';
|
||||
window.entityId = {{ person.id|e('js') }};
|
||||
{% elseif household is defined %}
|
||||
window.entityType = 'household';
|
||||
window.entityId = {{ household.id|e('js') }};
|
||||
{% endif %}
|
||||
|
||||
{% if 'edit' in app.request.get('_route') %}
|
||||
window.addressId = {{ app.request.get('address_id')|e('js') }};
|
||||
window.mode = 'edit';
|
||||
{% elseif mode is defined and mode == 'edit' %}
|
||||
window.addressId = {{ address_id|e('js') }};
|
||||
window.mode = 'edit';
|
||||
{% endif %}
|
||||
|
||||
{% if backUrl is not defined %}
|
||||
{% if person is defined %}
|
||||
window.backUrl = '{{ path('chill_person_address_list', { 'person_id': person.id })|e('js') }}';
|
||||
{% elseif household is defined %}
|
||||
window.backUrl = '{{ path('chill_person_household_addresses', { 'household_id': household.id })|e('js') }}';
|
||||
{% endif %}
|
||||
{% else %}
|
||||
window.backUrl = '{{ backUrl|e('js') }}';
|
||||
{% endif %}
|
||||
|
||||
{% if modalTitle is defined %}
|
||||
window.modalTitle = '{{ modalTitle|trans|e('js') }}';
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if buttonText is defined %}
|
||||
window.buttonText = '{{ buttonText|trans|e('js') }}';
|
||||
{% endif %}
|
||||
|
||||
{% if buttonSize is defined %}
|
||||
window.buttonSize = '{{ buttonSize|e('js') }}';
|
||||
{% endif %}
|
||||
|
||||
{% if buttonDisplayText is defined and buttonDisplayText == false %}
|
||||
window.buttonDisplayText = false;
|
||||
{% endif %}
|
||||
|
||||
{% if binModalStep1 is defined and binModalStep1 == false %}
|
||||
window.binModalStep1 = false;
|
||||
{% endif %}
|
||||
|
||||
{% if binModalStep2 is defined and binModalStep2 == false %}
|
||||
window.binModalStep2 = false;
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
{{ encore_entry_script_tags('vue_address') }}
|
||||
{{ encore_entry_link_tags('vue_address') }}
|
@@ -27,10 +27,15 @@
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
|
||||
binModalStep1: false,
|
||||
binModalStep2: false,
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'person', id: person.id },
|
||||
backUrl: path('chill_person_address_list', { 'person_id': person.id }),
|
||||
openPanesInModal: false,
|
||||
stickyActions: true,
|
||||
useValidFrom: true,
|
||||
} %}
|
||||
{#
|
||||
#}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
@@ -29,7 +29,9 @@
|
||||
<li style="margin: auto;">
|
||||
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'person', id: person.id },
|
||||
backUrl: path('chill_person_address_list', { 'person_id': person.id }),
|
||||
mode: 'new',
|
||||
buttonSize: 'btn-lg',
|
||||
buttonText: 'Add an address',
|
||||
@@ -64,28 +66,30 @@
|
||||
{% if address.validTo is not empty and address.validTo < previousRowFrom %}
|
||||
<div class="{{ 'row' ~ row ~ ' ' }}col-a action">
|
||||
|
||||
{# include vue_address component #}
|
||||
<a href="" class="btn btn-sm btn-create">{{ 'Insert an address'|trans }}</a></div>
|
||||
{# include vue_address component #}
|
||||
{#
|
||||
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
|
||||
address_id: address.id,
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'person', id: person.id },
|
||||
backUrl: path('chill_person_address_list', { 'person_id': person.id }),
|
||||
mode: 'edit',
|
||||
binModalStep1: false,
|
||||
binModalStep2: false,
|
||||
addressId: address.id,
|
||||
openPanesInModal: false,
|
||||
} %}
|
||||
#}
|
||||
|
||||
<div class="{{ 'row' ~ row ~ ' ' }}col-b"></div>
|
||||
<div class="{{ 'row' ~ row ~ ' ' }}col-c action">
|
||||
|
||||
{# include vue_address component #}
|
||||
<a href="" class="btn btn-sm btn-create">{{ 'Insert an address'|trans }}</a></div>
|
||||
{# include vue_address component #}
|
||||
{#
|
||||
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
|
||||
address_id: address.id,
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'person', id: person.id },
|
||||
backUrl: path('chill_person_address_list', { 'person_id': person.id }),
|
||||
mode: 'edit',
|
||||
binModalStep1: false,
|
||||
binModalStep2: false,
|
||||
addressId: address.id,
|
||||
openPanesInModal: false,
|
||||
} %}
|
||||
#}
|
||||
|
||||
@@ -109,7 +113,6 @@
|
||||
}) }}
|
||||
<ul class="record_actions">
|
||||
|
||||
{# include vue_address component #}
|
||||
<li><a href="{{ path('chill_person_address_edit', { 'person_id': person.id, 'address_id' : address.id } ) }}" class="btn btn-edit"></a></li>
|
||||
|
||||
</ul>
|
||||
@@ -160,7 +163,6 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{# include vue_address component #}
|
||||
<a class="btn btn-create"
|
||||
href="{{ path('chill_person_address_new', { 'person_id' : person.id } ) }}">
|
||||
{{ 'Add an address'|trans }}
|
||||
|
@@ -27,10 +27,16 @@
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
|
||||
binModalStep1: false,
|
||||
binModalStep2: false,
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'person', id: person.id },
|
||||
backUrl: path('chill_person_address_list', { 'person_id': person.id }),
|
||||
openPanesInModal: false,
|
||||
stickyActions: true,
|
||||
useValidFrom: true,
|
||||
} %}
|
||||
{#
|
||||
useValidTo: true,
|
||||
#}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
@@ -16,6 +16,10 @@
|
||||
'replace' Twig\Markup,
|
||||
'after' Twig\Markup
|
||||
]
|
||||
* customArea [
|
||||
'beforeLabel' Twig\Markup,
|
||||
'afterLabel' Twig\Markup,
|
||||
]
|
||||
#}
|
||||
|
||||
{% macro raw(person, options) %}
|
||||
@@ -37,11 +41,22 @@
|
||||
<div class="denomination {{ 'h' ~ options['hLevel'] }}">
|
||||
{%- if options['addLink'] and is_granted('CHILL_PERSON_SEE', person) -%}
|
||||
<a href="{{ chill_path_add_return_path('chill_person_view', { 'person_id': person.id }) }}">
|
||||
{{ _self.raw(person, options) }}
|
||||
</a>
|
||||
{%- else -%}
|
||||
{{ _self.raw(person, options) }}
|
||||
{%- endif -%}
|
||||
|
||||
{% if options['customArea']['beforeLabel'] is defined %}
|
||||
{{ options['customArea']['beforeLabel'] }}
|
||||
{% endif %}
|
||||
|
||||
{{ _self.raw(person, options) }}
|
||||
|
||||
{% if options['customArea']['afterLabel'] is defined %}
|
||||
{{ options['customArea']['afterLabel'] }}
|
||||
{% endif %}
|
||||
|
||||
{%- if options['addLink'] and is_granted('CHILL_PERSON_SEE', person) -%}
|
||||
</a>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if options['addEntity'] -%}
|
||||
<span class="badge rounded-pill bg-secondary">{{ 'Person'|trans }}</span>
|
||||
{%- endif -%}
|
||||
@@ -131,10 +146,10 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if options['addCenter'] %}
|
||||
{% if options['addCenter'] and person|chill_resolve_center is not null %}
|
||||
<li>
|
||||
<i class="fa fa-li fa-long-arrow-right"></i>
|
||||
{{ person.center }}
|
||||
{{ person|chill_resolve_center.name }}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@@ -4,7 +4,8 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ block('title') }}</h1>
|
||||
<h1>{{ member.person|chill_entity_render_string }}</h1>
|
||||
<h2 class="mb-5">{{ 'household.Edit his household'|trans }}</h2>
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
|
@@ -0,0 +1,65 @@
|
||||
{% macro addHolder(holder) %}
|
||||
{% if holder %}
|
||||
<span class="fa-stack fa-holder" title="{{ 'household.holder'|trans }}">
|
||||
<i class="fa fa-circle fa-stack-1x text-success"></i>
|
||||
<i class="fa fa-stack-1x">T</i>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col" style="flex-basis: 30%;">
|
||||
{{ member.person|chill_entity_render_box({
|
||||
'render': 'label',
|
||||
'addLink': true,
|
||||
'addInfo': true,
|
||||
'customArea': {
|
||||
'afterLabel': _self.addHolder(member.holder)
|
||||
}
|
||||
}) }}
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<div class="float-button top"><div class="box">
|
||||
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
|
||||
{% if customButtons['before'] is defined %}
|
||||
{{ customButtons['before'] }}
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
<a class="btn btn-sm btn-edit"
|
||||
title="{{ 'household.Edit member household'|trans }}"
|
||||
href="{{ chill_path_add_return_path('chill_person_household_member_edit', { 'id': member.id }) }}"></a>
|
||||
</li>
|
||||
|
||||
{% if customButtons['after'] is defined %}
|
||||
{{ customButtons['after'] }}
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="list-content fa-ul small ms-0">
|
||||
{% if member.startDate is not empty %}
|
||||
<li><i class="fa fa-long-arrow-right"></i>
|
||||
{{ 'Since %date%'|trans({'%date%': member.startDate|format_date('short') }) }}</li>
|
||||
{% endif %}
|
||||
{% if member.endDate is not empty %}
|
||||
<li><i class="fa fa-long-arrow-right"></i>
|
||||
{{ 'Until %date%'|trans({'%date%': member.endDate|format_date('short') }) }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{% if member.comment is not empty %}
|
||||
<blockquote class="chill-user-quote ms-0 mb-0" style="clear: right">
|
||||
{{ member.comment|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
|
||||
</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -7,9 +7,12 @@
|
||||
|
||||
<div>
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
|
||||
binModalStep1: false,
|
||||
binModalStep2: false,
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'household', id: household.id },
|
||||
backUrl: path('chill_person_household_addresses', { 'household_id': household.id }),
|
||||
openPanesInModal: false,
|
||||
stickyActions: true,
|
||||
useValidFrom: true,
|
||||
} %}
|
||||
</div>
|
||||
|
||||
|
@@ -7,10 +7,15 @@
|
||||
|
||||
<div>
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
|
||||
binModalStep1: false,
|
||||
binModalStep2: false,
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'household', id: household.id },
|
||||
backUrl: path('chill_person_household_addresses', { 'household_id': household.id }),
|
||||
openPanesInModal: false,
|
||||
stickyActions: true,
|
||||
useValidFrom: true,
|
||||
} %}
|
||||
{#
|
||||
#}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
@@ -15,13 +15,17 @@
|
||||
<li style="margin: auto;">
|
||||
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'household', id: household.id },
|
||||
backUrl: path('chill_person_household_addresses', { 'household_id': household.id }),
|
||||
mode: 'new',
|
||||
backUrl: chill_path_add_return_path('chill_person_household_address_move', { 'household_id': household.id }),
|
||||
buttonSize: 'btn-lg',
|
||||
buttonText: 'Move household',
|
||||
modalTitle: 'Move household',
|
||||
} %}
|
||||
{#
|
||||
useValidFrom: true,
|
||||
#}
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
@@ -41,7 +45,6 @@
|
||||
<div class="{{ 'row' ~ row ~ ' ' }}col-b"></div>
|
||||
<div class="{{ 'row' ~ row ~ ' ' }}col-c action">
|
||||
|
||||
{# include vue_address component #}
|
||||
<a href="" class="btn btn-sm btn-create">{{ 'Insert an address'|trans }}</a></div>
|
||||
|
||||
<div class="date">
|
||||
@@ -63,8 +66,6 @@
|
||||
}) }}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
|
||||
{# include vue_address component #}
|
||||
<a href="{{ path('chill_person_household_address_edit', { 'household_id': household.id, 'address_id' : address.id } ) }}"
|
||||
class="btn btn-edit"></a>
|
||||
|
||||
@@ -91,7 +92,6 @@
|
||||
</li>
|
||||
<li>
|
||||
|
||||
{# include vue_address component #}
|
||||
<a class="btn btn-create"
|
||||
href="{{ chill_path_add_return_path('chill_person_household_address_move', { 'household_id': household.id }) }}">
|
||||
{{ 'Move household'|trans }}
|
||||
|
@@ -20,24 +20,21 @@
|
||||
<i class="fa fa-fw fa-users" title="{{- 'household.Current household members'|trans }}"></i>
|
||||
</span>
|
||||
|
||||
{%- for m in members|slice(0, 5) -%}
|
||||
{%- for m in members -%}
|
||||
<span
|
||||
class="badge-member{%- if m.holder %} holder{% endif -%}{%- if m.position.ordering >= 2 %} child{% endif -%}"
|
||||
title="{{ m.position.label.fr }}">
|
||||
{%- if m.holder %}
|
||||
<span class="badge bg-chill-light-gray text-chill-gray">
|
||||
{{ 'household.holder'|trans }}
|
||||
</span>
|
||||
{% endif -%}
|
||||
{{- m.person|chill_entity_render_box({'addLink': false}) -}}
|
||||
<a href="{{ path('chill_person_view', { person_id: m.person.id}) }}">
|
||||
{%- if m.holder %}
|
||||
<span class="fa-stack fa-holder" title="{{ 'household.holder'|trans }}">
|
||||
<i class="fa fa-circle fa-stack-1x text-success"></i>
|
||||
<i class="fa fa-stack-1x">T</i>
|
||||
</span>
|
||||
{% endif -%}
|
||||
{{- m.person|chill_entity_render_box() -}}
|
||||
</a>
|
||||
</span>
|
||||
{%- endfor -%}
|
||||
|
||||
{% if members|length > 5 %}
|
||||
<span class="current-members-more">
|
||||
{{ 'household.and x other persons'|trans({'x': members|length-5}) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -17,4 +17,8 @@
|
||||
'layout': '@ChillPerson/menu.html.twig',
|
||||
'args' : { 'household': household }
|
||||
}) }}
|
||||
|
||||
{% block block_post_menu %}
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
{% block title 'household.Edit household members'|trans %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10 col-xxl household-members">
|
||||
<div class="household-members">
|
||||
|
||||
<h1>{{ block('title') }}</h1>
|
||||
<div id="household_members_editor"></div>
|
||||
@@ -22,5 +22,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('vue_household_members_editor') }}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('vue_household_members_editor') }}
|
||||
{% endblock %}
|
||||
|
@@ -2,213 +2,196 @@
|
||||
|
||||
{% block title 'household.Household summary'|trans %}
|
||||
|
||||
{% block block_post_menu %}
|
||||
<div class="block-post-menu"></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="household-summary">
|
||||
|
||||
<h1>{{ block('title') }}</h1>
|
||||
{#
|
||||
<h1>{{ block('title') }}</h1>
|
||||
<h2>{{ 'household.Current address'|trans }}</h2>
|
||||
#}
|
||||
|
||||
<h2>{{ 'household.Current address'|trans }}</h2>
|
||||
{% set address = household.currentAddress %}
|
||||
|
||||
{% set address = household.currentAddress %}
|
||||
<div class="row household-resume">
|
||||
<div class="item-bloc col-5 col-address">
|
||||
<h2>{{ 'Address'|trans }}</h2>
|
||||
|
||||
{% if address is empty %}
|
||||
<p class="chill-no-data-statement">{{ 'household.Household does not have any address currently'|trans }}</p>
|
||||
{% else %}
|
||||
<div>
|
||||
{{ address|chill_entity_render_box({'multiline': true}) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>{{ 'household.Household members'|trans }}</h2>
|
||||
|
||||
{% if form is not null %}
|
||||
{{ form_start(form) }}
|
||||
|
||||
{{ form_row(form.commentMembers) }}
|
||||
|
||||
<div id="waitingForBirthContainer">
|
||||
{{ form_row(form.waitingForBirth) }}
|
||||
</div>
|
||||
|
||||
<div id="waitingForBirthDateContainer">
|
||||
{{ form_row(form.waitingForBirthDate) }}
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button type="submit" class="btn btn-save">
|
||||
{{ 'Save'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
{% else %}
|
||||
|
||||
{% if not household.commentMembers.isEmpty() %}
|
||||
{{ household.commentMembers|chill_entity_render_box }}
|
||||
{% endif %}
|
||||
|
||||
{% if household.waitingForBirth %}
|
||||
{% if household.waitingForBirthDate is not null %}
|
||||
{{ 'household.Expecting for birth on date'|trans({ 'date': household.waitingForBirthDate|format_date('long') }) }}
|
||||
{% else %}
|
||||
{{ 'household.Expecting for birth'|trans }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">
|
||||
{{ 'household.Any expecting birth'|trans }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_summary', { 'household_id': household.id, 'edit': 1 }) }}"
|
||||
class="btn btn-edit">
|
||||
{{ 'household.Comment and expecting birth'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% for p in positions %}
|
||||
<h3>{{ p.label|localize_translatable_string }}</h3>
|
||||
|
||||
{% if false == p.shareHousehold %}
|
||||
<p>{{ 'household.Those members does not share address'|trans }}</p>
|
||||
{% endif %}
|
||||
|
||||
{%- set members = household.currentMembersByPosition(p) %}
|
||||
{% if members|length > 0 %}
|
||||
<div class="flex-table list-household-members">
|
||||
{% for m in members %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<div>
|
||||
{{ m.person|chill_entity_render_box({'addLink': true}) }}
|
||||
{% if m.holder %}
|
||||
<span class="badge bg-primary">{{ 'household.holder'|trans }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
{{ 'Born the date'|trans({ 'gender': m.person.gender, 'birthdate': m.person.birthdate|format_date('long') }) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
{% if m.startDate is not empty %}
|
||||
<li>{{ 'Since %date%'|trans({'%date%': m.startDate|format_date('long') }) }}</li>
|
||||
{% endif %}
|
||||
{% if m.endDate is not empty %}
|
||||
<li>{{ 'Until %date%'|trans({'%date%': m.endDate|format_date('long') }) }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_member_edit', { 'id': m.id }) }}"
|
||||
class="btn btn-edit" />
|
||||
{{ 'household.Update membership'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'persons': [ m.person.id ], 'household': household.id} ) }}"
|
||||
class="btn btn-misc" />
|
||||
<i class="fa fa-arrows-h"></i>
|
||||
{{ 'household.Change position'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'persons': [ m.person.id ], 'allow_leave_without_household': true } ) }}"
|
||||
class="btn btn-misc" />
|
||||
<i class="fa fa-sign-out"></i>
|
||||
{{ 'household.Leave'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if m.comment is not empty %}
|
||||
<div class="item-row comment">
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ m.comment|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'household.Any persons into this position'|trans }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% set members = household.nonCurrentMembersByPosition(p) %}
|
||||
{% if members|length > 0 %}
|
||||
<p><!-- force a space after table --></p>
|
||||
<button class="btn btn-green" type="button" data-bs-toggle="collapse" data-bs-target="#nonCurrent_{{ p.id }}" aria-expanded="false" aria-controls="collapse non current members">
|
||||
{{ 'household.Show future or past memberships'|trans({'length': members|length}) }}
|
||||
</button>
|
||||
|
||||
<div id="nonCurrent_{{ p.id }}" class="collapse">
|
||||
<div class="flex-table list-household-members">
|
||||
{% for m in members %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<div>
|
||||
{{ m.person|chill_entity_render_box({'addLink': true}) }}
|
||||
{% if m.holder %}
|
||||
<span class="badge bg-primary">{{ 'household.holder'|trans }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
{{ 'Born the date'|trans({ 'gender': m.person.gender, 'birthdate': m.person.birthdate|format_date('long') }) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
{% if m.startDate is not empty %}
|
||||
<li>{{ 'Since %date%'|trans({'%date%': m.startDate|format_date('long') }) }}</li>
|
||||
{% endif %}
|
||||
{% if m.endDate is not empty %}
|
||||
<li>{{ 'Until %date%'|trans({'%date%': m.endDate|format_date('long') }) }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_member_edit', { 'id': m.id }) }}"
|
||||
class="btn btn-edit">
|
||||
{{ 'household.Update membership'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if m.comment is not empty %}
|
||||
<div class="item-row comment">
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ m.comment|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
</div>
|
||||
{% if address is empty %}
|
||||
<p class="chill-no-data-statement">{{ 'household.Household does not have any address currently'|trans }}</p>
|
||||
{% else %}
|
||||
{{ address|chill_entity_render_box({'multiline': true, 'extended_infos': true }) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<ul class="list-inline text-right mt-2">
|
||||
<li class="list-inline-item">
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'household', id: household.id },
|
||||
backUrl: path('chill_person_household_summary', { 'household_id': household.id }),
|
||||
hideAddress: true,
|
||||
mode: 'new',
|
||||
buttonSize: 'btn-sm',
|
||||
buttonText: 'Move household',
|
||||
modalTitle: 'Move household',
|
||||
buttonDisplayText: false,
|
||||
} %}
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a class="btn btn-secondary btn-sm" title="{{ "Addresses history"|trans }}"
|
||||
href="{{ path('chill_person_household_addresses', { 'household_id': household.id } ) }}">
|
||||
<i class="fa fa-list fa-fw"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% if address is not empty %}
|
||||
<div class="item-bloc col-7 col-comment">
|
||||
{% if form is null %}
|
||||
|
||||
{% if household.waitingForBirth or not household.commentMembers.isEmpty() %}
|
||||
<div class="p-4 bg-light">
|
||||
{% if household.waitingForBirth %}
|
||||
<i class="fa fa-check-square-o pe-2"></i>
|
||||
{% if household.waitingForBirthDate is not null %}
|
||||
{{ 'household.Expecting for birth on date'|trans({ 'date': household.waitingForBirthDate|format_date('long') }) }}
|
||||
{% else %}
|
||||
{{ 'household.Expecting for birth'|trans }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if not household.commentMembers.isEmpty() %}
|
||||
{{ household.commentMembers|chill_entity_render_box }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not household.commentMembers.isEmpty() %}
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_summary', { 'household_id': household.id, 'edit': 1 }) }}"
|
||||
class="btn btn-edit btn-block">
|
||||
{{ 'household.Edit comment and expecting birth'|trans }}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_summary', { 'household_id': household.id, 'edit': 1 }) }}"
|
||||
class="btn btn-create btn-block">
|
||||
{{ 'household.New comment and expecting birth'|trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{{ form_start(form) }}
|
||||
<div id="waitingForBirthContainer">
|
||||
{{ form_widget(form.waitingForBirth) }}
|
||||
</div>
|
||||
<div id="waitingForBirthDateContainer">
|
||||
{{ form_widget(form.waitingForBirthDate) }}
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
{{ form_widget(form.commentMembers) }}
|
||||
</div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button type="submit" class="btn btn-save">
|
||||
{{ 'Save'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(form) }}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<h2 class="my-5">{{ 'household.Household members'|trans }}</h2>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'household': household.id }) }}"
|
||||
class="btn btn-create">
|
||||
{{ 'household.Add a member'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% for p in positions %}
|
||||
<div class="mb-5">
|
||||
<h3>{{ p.label|localize_translatable_string }}
|
||||
{% if false == p.shareHousehold %}
|
||||
<i class="chill-help-tooltip" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true"
|
||||
title="{{ 'household.Those members does not share address'|trans }}"></i>
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
||||
{%- set members = household.currentMembersByPosition(p) %}
|
||||
|
||||
{% macro customButtons(member, household) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'persons': [ member.person.id ], 'allow_leave_without_household': true } ) }}"
|
||||
class="btn btn-sm btn-unlink" title="{{ 'household.person.leave'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'persons': [ member.person.id ], 'household': household.id} ) }}"
|
||||
class="btn btn-sm btn-misc" title="{{ 'household.Change position'|trans }}"><i class="fa fa-arrows-h"></i></a>
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
||||
{% if members|length > 0 %}
|
||||
<div class="flex-table list-household-members">
|
||||
{% for m in members %}
|
||||
{% include '@ChillPerson/Household/_render_member.html.twig' with {
|
||||
'member': m,
|
||||
'customButtons': { 'after': _self.customButtons(m, household) }
|
||||
} %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'household.Any persons into this position'|trans }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% set members = household.nonCurrentMembersByPosition(p) %}
|
||||
{% if members|length > 0 %}
|
||||
|
||||
<style>
|
||||
button[aria-expanded="true"] > span.folded,
|
||||
button[aria-expanded="false"] > span.unfolded { display: none; }
|
||||
button[aria-expanded="false"] > span.folded,
|
||||
button[aria-expanded="true"] > span.unfolded { display: inline; }
|
||||
</style>
|
||||
<div class="accordion" id="nonCurrent">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading_{{ p.id }}">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse_{{ p.id }}"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse_{{ p.id }}">
|
||||
<span class="folded">{{ 'household.Show future or past memberships'|trans({'length': members|length}) }}</span>
|
||||
<span class="unfolded text-secondary">{{ 'household.Hide memberships'|trans }}</span>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse_{{ p.id }}"
|
||||
class="accordion-collapse collapse"
|
||||
aria-labelledby="heading_{{ p.id }}"
|
||||
data-bs-parent="#nonCurrent">
|
||||
<div class="flex-table my-0 list-household-members">
|
||||
{% for m in members %}
|
||||
{% include '@ChillPerson/Household/_render_member.html.twig' with { 'member': m } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'household': household.id }) }}"
|
||||
class="btn btn-create">
|
||||
{{ 'household.Add a member'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@@ -17,11 +17,11 @@
|
||||
{%- endif -%}
|
||||
</div>
|
||||
<div class="text-md-end">
|
||||
{%- if chill_person.fields.spoken_languages == 'visible' -%}
|
||||
{% if person|chill_resolve_center is not null%}
|
||||
<span class="open_sansbold">
|
||||
{{ 'Center'|trans|upper}} :
|
||||
</span>
|
||||
{{ person.center.name|upper }}
|
||||
{{ person|chill_resolve_center.name|upper }}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -64,10 +64,10 @@
|
||||
{{ form_start(form) }}
|
||||
|
||||
|
||||
{{ form_row(form.firstName, { 'label' : 'First name'|trans }) }}
|
||||
|
||||
{{ form_row(form.lastName, { 'label' : 'Last name'|trans }) }}
|
||||
|
||||
{{ form_row(form.firstName, { 'label' : 'First name'|trans }) }}
|
||||
|
||||
{% if form.altNames is defined %}
|
||||
{{ form_widget(form.altNames) }}
|
||||
{% endif %}
|
||||
@@ -76,12 +76,27 @@
|
||||
|
||||
{{ form_row(form.gender, { 'label' : 'Gender'|trans }) }}
|
||||
|
||||
{% if form.center is defined %}
|
||||
{{ form_row(form.center) }}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
{{ form_widget(form.editPerson, { 'attr': { 'class': 'btn btn-create' }}) }}
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.createPeriod, { 'attr': { 'class': 'btn btn-create' }}) }}
|
||||
<li class="dropdown">
|
||||
<a class="btn btn-create dropdown-toggle"
|
||||
href="#" role="button" id="newPersonMore" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{ 'Add the person'|trans }}
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="newPersonMore">
|
||||
<li>
|
||||
{{ form_widget(form.editPerson, { 'attr': { 'class': 'dropdown-item' }}) }}
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.createPeriod, { 'attr': { 'class': 'dropdown-item' }}) }}
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.createHousehold, { 'attr': { 'class': 'dropdown-item' }}) }}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@@ -20,71 +20,39 @@
|
||||
namespace Chill\PersonBundle\Search;
|
||||
|
||||
use Chill\MainBundle\Search\AbstractSearch;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\MainBundle\Search\SearchInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Chill\MainBundle\Search\ParsingException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Chill\MainBundle\Search\HasAdvancedSearchFormInterface;
|
||||
use Doctrine\ORM\Query;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
|
||||
class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
|
||||
HasAdvancedSearchFormInterface
|
||||
class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterface
|
||||
{
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var \Chill\MainBundle\Entity\User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
private $helper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var PaginatorFactory
|
||||
*/
|
||||
protected $paginatorFactory;
|
||||
protected EngineInterface $templating;
|
||||
protected PaginatorFactory $paginatorFactory;
|
||||
protected PersonACLAwareRepositoryInterface $personACLAwareRepository;
|
||||
|
||||
const NAME = "person_regular";
|
||||
|
||||
private const POSSIBLE_KEYS = [
|
||||
'_default', 'firstname', 'lastname', 'birthdate', 'birthdate-before',
|
||||
'birthdate-after', 'gender', 'nationality'
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
AuthorizationHelper $helper,
|
||||
PaginatorFactory $paginatorFactory)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->user = $tokenStorage->getToken()->getUser();
|
||||
$this->helper = $helper;
|
||||
EngineInterface $templating,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
PersonACLAwareRepositoryInterface $personACLAwareRepository
|
||||
) {
|
||||
$this->templating = $templating;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
|
||||
// throw an error if user is not a valid user
|
||||
if (!$this->user instanceof \Chill\MainBundle\Entity\User) {
|
||||
throw new \LogicException('The user provided must be an instance'
|
||||
. ' of Chill\MainBundle\Entity\User');
|
||||
}
|
||||
$this->personACLAwareRepository = $personACLAwareRepository;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -120,12 +88,14 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
|
||||
if ($format === 'html') {
|
||||
return $this->container->get('templating')->render('@ChillPerson/Person/list_with_period.html.twig',
|
||||
return $this->templating->render('@ChillPerson/Person/list_with_period.html.twig',
|
||||
array(
|
||||
'persons' => $this->search($terms, $start, $limit, $options),
|
||||
'pattern' => $this->recomposePattern($terms, array('nationality',
|
||||
'firstname', 'lastname', 'birthdate', 'gender',
|
||||
'birthdate-before','birthdate-after'), $terms['_domain']),
|
||||
'pattern' => $this->recomposePattern(
|
||||
$terms,
|
||||
\array_filter(self::POSSIBLE_KEYS, fn($item) => $item !== '_default'),
|
||||
$terms['_domain']
|
||||
),
|
||||
'total' => $total,
|
||||
'start' => $start,
|
||||
'search_name' => self::NAME,
|
||||
@@ -152,153 +122,81 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
|
||||
*/
|
||||
protected function search(array $terms, $start, $limit, array $options = array())
|
||||
{
|
||||
$qb = $this->createQuery($terms, 'search');
|
||||
|
||||
if ($options['simplify'] ?? false) {
|
||||
$qb->select(
|
||||
'p.id',
|
||||
$qb->expr()->concat(
|
||||
'p.firstName',
|
||||
$qb->expr()->literal(' '),
|
||||
'p.lastName'
|
||||
).'AS text'
|
||||
);
|
||||
} else {
|
||||
$qb->select('p');
|
||||
[
|
||||
'_default' => $default,
|
||||
'firstname' => $firstname,
|
||||
'lastname' => $lastname,
|
||||
'birthdate' => $birthdate,
|
||||
'birthdate-before' => $birthdateBefore,
|
||||
'birthdate-after' => $birthdateAfter,
|
||||
'gender' => $gender,
|
||||
'nationality' => $countryCode,
|
||||
|
||||
] = $terms + \array_fill_keys(self::POSSIBLE_KEYS, null);
|
||||
|
||||
foreach (['birthdateBefore', 'birthdateAfter', 'birthdate'] as $v) {
|
||||
if (NULL !== ${$v}) {
|
||||
try {
|
||||
${$v} = new \DateTime(${$v});
|
||||
} catch (\Exception $e) {
|
||||
throw new ParsingException('The date is '
|
||||
. 'not parsable', 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$qb
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($start);
|
||||
|
||||
//order by firstname, lastname
|
||||
|
||||
$qb
|
||||
->orderBy('p.firstName')
|
||||
->addOrderBy('p.lastName');
|
||||
|
||||
if ($options['simplify'] ?? false) {
|
||||
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
|
||||
} else {
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
return $this->personACLAwareRepository
|
||||
->findBySearchCriteria(
|
||||
$start,
|
||||
$limit,
|
||||
$options['simplify'] ?? false,
|
||||
$default,
|
||||
$firstname,
|
||||
$lastname,
|
||||
$birthdate,
|
||||
$birthdateBefore,
|
||||
$birthdateAfter,
|
||||
$gender,
|
||||
$countryCode,
|
||||
);
|
||||
}
|
||||
|
||||
protected function count(array $terms)
|
||||
protected function count(array $terms): int
|
||||
{
|
||||
$qb = $this->createQuery($terms);
|
||||
[
|
||||
'_default' => $default,
|
||||
'firstname' => $firstname,
|
||||
'lastname' => $lastname,
|
||||
'birthdate' => $birthdate,
|
||||
'birthdate-before' => $birthdateBefore,
|
||||
'birthdate-after' => $birthdateAfter,
|
||||
'gender' => $gender,
|
||||
'nationality' => $countryCode,
|
||||
|
||||
$qb->select('COUNT(p.id)');
|
||||
] = $terms + \array_fill_keys(self::POSSIBLE_KEYS, null);
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
|
||||
private $_cacheQuery = array();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $terms
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
public function createQuery(array $terms)
|
||||
{
|
||||
//get from cache
|
||||
$cacheKey = md5(serialize($terms));
|
||||
if (array_key_exists($cacheKey, $this->_cacheQuery)) {
|
||||
return clone $this->_cacheQuery[$cacheKey];
|
||||
}
|
||||
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
|
||||
$qb->from('ChillPersonBundle:Person', 'p');
|
||||
|
||||
if (array_key_exists('firstname', $terms)) {
|
||||
$qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.firstName))', ':firstname'))
|
||||
->setParameter('firstname', '%'.$terms['firstname'].'%');
|
||||
}
|
||||
|
||||
if (array_key_exists('lastname', $terms)) {
|
||||
$qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.lastName))', ':lastname'))
|
||||
->setParameter('lastname', '%'.$terms['lastname'].'%');
|
||||
}
|
||||
|
||||
foreach (['birthdate', 'birthdate-before', 'birthdate-after'] as $key)
|
||||
if (array_key_exists($key, $terms)) {
|
||||
try {
|
||||
$date = new \DateTime($terms[$key]);
|
||||
} catch (\Exception $ex) {
|
||||
throw new ParsingException('The date is '
|
||||
. 'not parsable', 0, $ex);
|
||||
}
|
||||
|
||||
switch($key) {
|
||||
case 'birthdate':
|
||||
$qb->andWhere($qb->expr()->eq('p.birthdate', ':birthdate'))
|
||||
->setParameter('birthdate', $date);
|
||||
break;
|
||||
case 'birthdate-before':
|
||||
$qb->andWhere($qb->expr()->lt('p.birthdate', ':birthdatebefore'))
|
||||
->setParameter('birthdatebefore', $date);
|
||||
break;
|
||||
case 'birthdate-after':
|
||||
$qb->andWhere($qb->expr()->gt('p.birthdate', ':birthdateafter'))
|
||||
->setParameter('birthdateafter', $date);
|
||||
break;
|
||||
default:
|
||||
throw new \LogicException("this case $key should not exists");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (array_key_exists('gender', $terms)) {
|
||||
if (!in_array($terms['gender'], array(Person::MALE_GENDER, Person::FEMALE_GENDER))) {
|
||||
throw new ParsingException('The gender '
|
||||
.$terms['gender'].' is not accepted. Should be "'.Person::MALE_GENDER
|
||||
.'" or "'.Person::FEMALE_GENDER.'"');
|
||||
}
|
||||
|
||||
$qb->andWhere($qb->expr()->eq('p.gender', ':gender'))
|
||||
->setParameter('gender', $terms['gender']);
|
||||
}
|
||||
|
||||
if (array_key_exists('nationality', $terms)) {
|
||||
try {
|
||||
$country = $this->em->createQuery('SELECT c FROM '
|
||||
. 'ChillMainBundle:Country c WHERE '
|
||||
. 'LOWER(c.countryCode) LIKE :code')
|
||||
->setParameter('code', $terms['nationality'])
|
||||
->getSingleResult();
|
||||
} catch (\Doctrine\ORM\NoResultException $ex) {
|
||||
throw new ParsingException('The country code "'.$terms['nationality'].'" '
|
||||
. ', used in nationality, is unknow', 0, $ex);
|
||||
}
|
||||
|
||||
$qb->andWhere($qb->expr()->eq('p.nationality', ':nationality'))
|
||||
->setParameter('nationality', $country);
|
||||
}
|
||||
|
||||
if ($terms['_default'] !== '') {
|
||||
$grams = explode(' ', $terms['_default']);
|
||||
|
||||
foreach($grams as $key => $gram) {
|
||||
$qb->andWhere($qb->expr()
|
||||
->like('p.fullnameCanonical', 'UNACCENT(LOWER(:default_'.$key.'))'))
|
||||
->setParameter('default_'.$key, '%'.$gram.'%');
|
||||
foreach (['birthdateBefore', 'birthdateAfter', 'birthdate'] as $v) {
|
||||
if (NULL !== ${$v}) {
|
||||
try {
|
||||
${$v} = new \DateTime(${$v});
|
||||
} catch (\Exception $e) {
|
||||
throw new ParsingException('The date is '
|
||||
. 'not parsable', 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//restraint center for security
|
||||
$reachableCenters = $this->helper->getReachableCenters($this->user,
|
||||
new Role('CHILL_PERSON_SEE'));
|
||||
$qb->andWhere($qb->expr()
|
||||
->in('p.center', ':centers'))
|
||||
->setParameter('centers', $reachableCenters)
|
||||
;
|
||||
|
||||
$this->_cacheQuery[$cacheKey] = $qb;
|
||||
|
||||
return clone $qb;
|
||||
return $this->personACLAwareRepository
|
||||
->countBySearchCriteria(
|
||||
$default,
|
||||
$firstname,
|
||||
$lastname,
|
||||
$birthdate,
|
||||
$birthdateBefore,
|
||||
$birthdateAfter,
|
||||
$gender,
|
||||
$countryCode,
|
||||
);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
@@ -391,4 +289,10 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
|
||||
return 'Search within persons';
|
||||
}
|
||||
|
||||
public static function getAlias(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -2,90 +2,32 @@
|
||||
|
||||
namespace Chill\PersonBundle\Search;
|
||||
|
||||
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Search\AbstractSearch;
|
||||
use Chill\MainBundle\Search\SearchInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
|
||||
/**
|
||||
* Class SimilarityPersonSearch
|
||||
*
|
||||
* @package Chill\PersonBundle\Search
|
||||
*/
|
||||
class SimilarityPersonSearch extends AbstractSearch
|
||||
{
|
||||
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var \Chill\MainBundle\Entity\User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
private $helper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var PaginatorFactory
|
||||
*/
|
||||
protected $paginatorFactory;
|
||||
|
||||
protected PaginatorFactory $paginatorFactory;
|
||||
|
||||
private PersonACLAwareRepositoryInterface $personACLAwareRepository;
|
||||
|
||||
private EngineInterface $templating;
|
||||
|
||||
const NAME = "person_similarity";
|
||||
|
||||
/**
|
||||
*
|
||||
* @var PersonSearch
|
||||
*/
|
||||
private $personSearch;
|
||||
|
||||
|
||||
/**
|
||||
* SimilarityPersonSearch constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em
|
||||
* @param TokenStorageInterface $tokenStorage
|
||||
* @param AuthorizationHelper $helper
|
||||
* @param PaginatorFactory $paginatorFactory
|
||||
* @param PersonSearch $personSearch
|
||||
*/
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
AuthorizationHelper $helper,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
PersonSearch $personSearch)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->user = $tokenStorage->getToken()->getUser();
|
||||
$this->helper = $helper;
|
||||
PersonACLAwareRepositoryInterface $personACLAwareRepository,
|
||||
EngineInterface $templating
|
||||
) {
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
$this->personSearch = $personSearch;
|
||||
|
||||
// throw an error if user is not a valid user
|
||||
if (!$this->user instanceof \Chill\MainBundle\Entity\User) {
|
||||
throw new \LogicException('The user provided must be an instance'
|
||||
. ' of Chill\MainBundle\Entity\User');
|
||||
}
|
||||
$this->personACLAwareRepository = $personACLAwareRepository;
|
||||
$this->templating = $templating;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-PHPdoc)
|
||||
* @see \Chill\MainBundle\Search\SearchInterface::getOrder()
|
||||
@@ -94,7 +36,7 @@ class SimilarityPersonSearch extends AbstractSearch
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-PHPdoc)
|
||||
* @see \Chill\MainBundle\Search\SearchInterface::isActiveByDefault()
|
||||
@@ -103,30 +45,27 @@ class SimilarityPersonSearch extends AbstractSearch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function supports($domain, $format)
|
||||
{
|
||||
return 'person' === $domain;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $terms
|
||||
* @param int $start
|
||||
* @param int $limit
|
||||
* @param array $options
|
||||
* @param string $format
|
||||
* @return array
|
||||
*/
|
||||
public function renderResult(array $terms, $start = 0, $limit = 50, array $options = array(), $format = 'html')
|
||||
{
|
||||
$total = $this->count($terms);
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
|
||||
if ($format === 'html')
|
||||
{
|
||||
if ($total !== 0)
|
||||
{
|
||||
return $this->container->get('templating')->render('ChillPersonBundle:Person:list.html.twig',
|
||||
|
||||
if ($format === 'html') {
|
||||
if ($total !== 0) {
|
||||
return $this->templating->render('ChillPersonBundle:Person:list.html.twig',
|
||||
array(
|
||||
'persons' => $this->search($terms, $start, $limit, $options),
|
||||
'pattern' => $this->recomposePattern($terms, array('nationality',
|
||||
@@ -143,9 +82,8 @@ class SimilarityPersonSearch extends AbstractSearch
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
} elseif ($format === 'json')
|
||||
{
|
||||
|
||||
} elseif ($format === 'json') {
|
||||
return [
|
||||
'results' => $this->search($terms, $start, $limit, \array_merge($options, [ 'simplify' => true ])),
|
||||
'pagination' => [
|
||||
@@ -154,8 +92,7 @@ class SimilarityPersonSearch extends AbstractSearch
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $pattern
|
||||
@@ -166,101 +103,12 @@ class SimilarityPersonSearch extends AbstractSearch
|
||||
*/
|
||||
protected function search(array $terms, $start, $limit, array $options = array())
|
||||
{
|
||||
$qb = $this->createQuery($terms, 'search');
|
||||
|
||||
|
||||
if ($options['simplify'] ?? false) {
|
||||
$qb->select(
|
||||
'sp.id',
|
||||
$qb->expr()->concat(
|
||||
'sp.firstName',
|
||||
$qb->expr()->literal(' '),
|
||||
'sp.lastName'
|
||||
).'AS text'
|
||||
);
|
||||
} else {
|
||||
$qb->select('sp');
|
||||
}
|
||||
|
||||
$qb
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($start);
|
||||
|
||||
//order by firstname, lastname
|
||||
|
||||
$qb
|
||||
->orderBy('sp.firstName')
|
||||
->addOrderBy('sp.lastName');
|
||||
|
||||
if ($options['simplify'] ?? false) {
|
||||
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
|
||||
} else {
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
return $this->personACLAwareRepository
|
||||
->findBySimilaritySearch($terms['_default'], $start, $limit, $options['simplify'] ?? false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function count(array $terms)
|
||||
{
|
||||
$qb = $this->createQuery($terms);
|
||||
|
||||
|
||||
$qb->select('COUNT(sp.id)');
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
return $this->personACLAwareRepository->countBySimilaritySearch($terms['_default']);
|
||||
}
|
||||
|
||||
|
||||
private $_cacheQuery = array();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $terms
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
protected function createQuery(array $terms)
|
||||
{
|
||||
//get from cache
|
||||
$cacheKey = md5(serialize($terms));
|
||||
if (array_key_exists($cacheKey, $this->_cacheQuery)) {
|
||||
return clone $this->_cacheQuery[$cacheKey];
|
||||
}
|
||||
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
|
||||
$qb ->select('sp')
|
||||
->from('ChillPersonBundle:Person', 'sp');
|
||||
|
||||
if ($terms['_default'] !== '') {
|
||||
$grams = explode(' ', $terms['_default']);
|
||||
|
||||
foreach($grams as $key => $gram) {
|
||||
$qb->andWhere('SIMILARITY(sp.fullnameCanonical, UNACCENT(LOWER(:default_'.$key.')) ) >= 0.15')
|
||||
->setParameter('default_'.$key, '%'.$gram.'%');
|
||||
}
|
||||
|
||||
$qb->andWhere($qb->expr()
|
||||
->notIn(
|
||||
'sp.id',
|
||||
$this->personSearch
|
||||
->createQuery($terms)
|
||||
->addSelect('p.id')
|
||||
->getDQL()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//restraint center for security
|
||||
$reachableCenters = $this->helper->getReachableCenters($this->user,
|
||||
new Role('CHILL_PERSON_SEE'));
|
||||
$qb->andWhere($qb->expr()
|
||||
->in('sp.center', ':centers'))
|
||||
->setParameter('centers', $reachableCenters)
|
||||
;
|
||||
|
||||
$this->_cacheQuery[$cacheKey] = $qb;
|
||||
|
||||
return clone $qb;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -4,73 +4,101 @@ namespace Chill\PersonBundle\Security\Authorization;
|
||||
|
||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
|
||||
{
|
||||
protected AuthorizationHelper $helper;
|
||||
|
||||
public const SEE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE';
|
||||
/**
|
||||
* details are for seeing:
|
||||
*
|
||||
* * SocialIssues
|
||||
*/
|
||||
public const SEE_DETAILS = 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE_DETAILS';
|
||||
public const CREATE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_CREATE';
|
||||
public const EDIT = 'CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE';
|
||||
public const DELETE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_DELETE';
|
||||
|
||||
/**
|
||||
* @param AuthorizationHelper $helper
|
||||
* Give all the right above
|
||||
*/
|
||||
public function __construct(AuthorizationHelper $helper)
|
||||
{
|
||||
$this->helper = $helper;
|
||||
public const FULL = 'CHILL_PERSON_ACCOMPANYING_PERIOD_FULL';
|
||||
|
||||
public const ALL = [
|
||||
self::SEE,
|
||||
self::SEE_DETAILS,
|
||||
self::CREATE,
|
||||
self::EDIT,
|
||||
self::DELETE,
|
||||
self::FULL,
|
||||
];
|
||||
|
||||
private VoterHelperInterface $voterHelper;
|
||||
|
||||
private Security $security;
|
||||
|
||||
public function __construct(
|
||||
Security $security,
|
||||
VoterHelperFactoryInterface $voterHelperFactory
|
||||
) {
|
||||
$this->security = $security;
|
||||
$this->voterHelper = $voterHelperFactory
|
||||
->generate(self::class)
|
||||
->addCheckFor(null, [self::CREATE])
|
||||
->addCheckFor(AccompanyingPeriod::class, self::ALL)
|
||||
->addCheckFor(Person::class, [self::SEE])
|
||||
->build();
|
||||
}
|
||||
|
||||
protected function supports($attribute, $subject)
|
||||
{
|
||||
return $subject instanceof AccompanyingPeriod;
|
||||
return $this->voterHelper->supports($attribute, $subject);
|
||||
}
|
||||
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
|
||||
{
|
||||
if (!$token->getUser() instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO take scopes into account
|
||||
if (count($subject->getPersons()) === 0) {
|
||||
return true;
|
||||
}
|
||||
foreach ($subject->getPersons() as $person) {
|
||||
// give access as soon as on center is reachable
|
||||
if ($this->helper->userHasAccess($token->getUser(), $person->getCenter(), $attribute)) {
|
||||
return true;
|
||||
if ($subject instanceof AccompanyingPeriod) {
|
||||
if (AccompanyingPeriod::STEP_DRAFT === $subject->getStep()) {
|
||||
// only creator can see, edit, delete, etc.
|
||||
if ($subject->getCreatedBy() === $token->getUser()
|
||||
|| NULL === $subject->getCreatedBy()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// if confidential, only the referent can see it
|
||||
if ($subject->isConfidential()) {
|
||||
return $token->getUser() === $subject->getUser();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getAttributes()
|
||||
{
|
||||
return [
|
||||
self::SEE
|
||||
];
|
||||
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
|
||||
}
|
||||
|
||||
public function getRoles()
|
||||
{
|
||||
return $this->getAttributes();
|
||||
return self::ALL;
|
||||
}
|
||||
|
||||
public function getRolesWithoutScope()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
public function getRolesWithHierarchy()
|
||||
{
|
||||
return [ 'Person' => $this->getRoles() ];
|
||||
return [ 'Accompanying period' => $this->getRoles() ];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,17 +22,15 @@ namespace Chill\PersonBundle\Security\Authorization;
|
||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
|
||||
{
|
||||
const CREATE = 'CHILL_PERSON_CREATE';
|
||||
@@ -41,51 +39,31 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
|
||||
const STATS = 'CHILL_PERSON_STATS';
|
||||
const LISTS = 'CHILL_PERSON_LISTS';
|
||||
const DUPLICATE = 'CHILL_PERSON_DUPLICATE';
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
protected $helper;
|
||||
|
||||
public function __construct(AuthorizationHelper $helper)
|
||||
{
|
||||
$this->helper = $helper;
|
||||
|
||||
protected VoterHelperInterface $voter;
|
||||
|
||||
public function __construct(
|
||||
VoterHelperFactoryInterface $voterFactory
|
||||
) {
|
||||
$this->voter = $voterFactory
|
||||
->generate(self::class)
|
||||
->addCheckFor(Center::class, [self::STATS, self::LISTS, self::DUPLICATE])
|
||||
->addCheckFor(Person::class, [self::CREATE, self::UPDATE, self::SEE, self::DUPLICATE])
|
||||
->addCheckFor(null, [self::CREATE] )
|
||||
->build()
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
protected function supports($attribute, $subject)
|
||||
{
|
||||
if ($subject instanceof Person) {
|
||||
return \in_array($attribute, [
|
||||
self::CREATE, self::UPDATE, self::SEE, self::DUPLICATE
|
||||
]);
|
||||
} elseif ($subject instanceof Center) {
|
||||
return \in_array($attribute, [
|
||||
self::STATS, self::LISTS, self::DUPLICATE
|
||||
]);
|
||||
} elseif ($subject === null) {
|
||||
return $attribute === self::CREATE;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return $this->voter->supports($attribute, $subject);
|
||||
}
|
||||
|
||||
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
{
|
||||
if (!$token->getUser() instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($subject === null) {
|
||||
$centers = $this->helper->getReachableCenters($token->getUser(),
|
||||
new Role($attribute));
|
||||
|
||||
return count($centers) > 0;
|
||||
}
|
||||
|
||||
return $this->helper->userHasAccess($token->getUser(), $subject, $attribute);
|
||||
return $this->voter->voteOnAttribute($attribute, $subject, $token);
|
||||
}
|
||||
|
||||
|
||||
private function getAttributes()
|
||||
{
|
||||
return array(self::CREATE, self::UPDATE, self::SEE, self::STATS, self::LISTS, self::DUPLICATE);
|
||||
@@ -100,7 +78,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
|
||||
{
|
||||
return $this->getAttributes();
|
||||
}
|
||||
|
||||
|
||||
public function getRolesWithHierarchy()
|
||||
{
|
||||
return [ 'Person' => $this->getRoles() ];
|
||||
|
@@ -19,6 +19,7 @@
|
||||
namespace Chill\PersonBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\PersonBundle\Entity\Household\Household;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||
@@ -61,6 +62,9 @@ class PersonNormalizer implements
|
||||
|
||||
public function normalize($person, string $format = null, array $context = array())
|
||||
{
|
||||
/** @var Household $household */
|
||||
$household = $person->getCurrentHousehold();
|
||||
|
||||
/** @var Person $person */
|
||||
return [
|
||||
'type' => 'person',
|
||||
@@ -69,6 +73,7 @@ class PersonNormalizer implements
|
||||
'firstName' => $person->getFirstName(),
|
||||
'lastName' => $person->getLastName(),
|
||||
'birthdate' => $this->normalizer->normalize($person->getBirthdate()),
|
||||
'deathdate' => $this->normalizer->normalize($person->getDeathdate()),
|
||||
'center' => $this->normalizer->normalize($person->getCenter()),
|
||||
'phonenumber' => $person->getPhonenumber(),
|
||||
'mobilenumber' => $person->getMobilenumber(),
|
||||
@@ -76,6 +81,7 @@ class PersonNormalizer implements
|
||||
'gender' => $person->getGender(),
|
||||
'gender_numeric' => $person->getGenderNumeric(),
|
||||
'current_household_address' => $this->normalizer->normalize($person->getCurrentHouseholdAddress()),
|
||||
'current_household_id' => $household ? $this->normalizer->normalize($household->getId()) : null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -89,7 +95,7 @@ class PersonNormalizer implements
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function supportsNormalization($data, string $format = null): bool
|
||||
{
|
||||
@@ -125,6 +131,7 @@ class PersonNormalizer implements
|
||||
|
||||
foreach ([
|
||||
'birthdate' => \DateTime::class,
|
||||
'deathdate' => \DateTime::class,
|
||||
'center' => Center::class
|
||||
] as $item => $class) {
|
||||
if (\array_key_exists($item, $data)) {
|
||||
|
@@ -62,6 +62,7 @@ class PersonRender extends AbstractChillEntityRender
|
||||
'address_multiline' => $options['address_multiline'] ?? false,
|
||||
'hLevel' => $options['hLevel'] ?? 3,
|
||||
'customButtons' => $options['customButtons'] ?? [],
|
||||
'customArea' => $options['customArea'] ?? [],
|
||||
];
|
||||
|
||||
return
|
||||
|
@@ -23,6 +23,7 @@
|
||||
namespace Chill\PersonBundle\Tests\Controller;
|
||||
|
||||
|
||||
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||
@@ -47,6 +48,8 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
|
||||
protected ?AccompanyingPeriod $period = NULL;
|
||||
|
||||
protected ?int $periodId = NULL;
|
||||
|
||||
/**
|
||||
* Setup before the first test of this class (see phpunit doc)
|
||||
*/
|
||||
@@ -70,15 +73,15 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
*
|
||||
* @dataProvider dataGenerateRandomAccompanyingCourse
|
||||
*/
|
||||
public function testAccompanyingCourseShow(int $personId, AccompanyingPeriod $period)
|
||||
public function testAccompanyingCourseShow(int $personId, int $periodId)
|
||||
{
|
||||
$c = $this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', $period->getId()));
|
||||
$c = $this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', $periodId));
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode(), "Test that the response of rest api has a status code ok (200)");
|
||||
|
||||
$data = \json_decode($response->getContent());
|
||||
$this->assertEquals($data->id, $period->getId(),
|
||||
$this->assertEquals($data->id, $periodId,
|
||||
"test that the response's data contains the id of the period"
|
||||
);
|
||||
$this->assertGreaterThan(0, $data->participations);
|
||||
@@ -326,14 +329,16 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
*
|
||||
* @dataProvider dataGenerateRandomAccompanyingCourse
|
||||
*/
|
||||
public function testAccompanyingPeriodPatch(int $personId, AccompanyingPeriod $period)
|
||||
public function testAccompanyingPeriodPatch(int $personId, int $periodId)
|
||||
{
|
||||
$period = self::$container->get(AccompanyingPeriodRepository::class)
|
||||
->find($periodId);
|
||||
$initialValueEmergency = $period->isEmergency();
|
||||
$em = self::$container->get(EntityManagerInterface::class);
|
||||
|
||||
$this->client->request(
|
||||
Request::METHOD_PATCH,
|
||||
sprintf('/api/1.0/person/accompanying-course/%d.json', $period->getId()),
|
||||
sprintf('/api/1.0/person/accompanying-course/%d.json', $periodId),
|
||||
[], // parameters
|
||||
[], // files
|
||||
[], // server parameters
|
||||
@@ -343,11 +348,10 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$period = $em->getRepository(AccompanyingPeriod::class)
|
||||
->find($period->getId());
|
||||
$em->refresh($period);
|
||||
->find($periodId);
|
||||
$this->assertEquals(!$initialValueEmergency, $period->isEmergency());
|
||||
|
||||
// restore the initial valud
|
||||
// restore the initial value
|
||||
$period->setEmergency($initialValueEmergency);
|
||||
$em->flush();
|
||||
}
|
||||
@@ -361,12 +365,32 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
$em = static::$container->get(EntityManagerInterface::class);
|
||||
$center = $em->getRepository(Center::class)
|
||||
->findOneBy(array('name' => 'Center A'));
|
||||
$qb = $em->createQueryBuilder();
|
||||
|
||||
$personIds = $em->createQuery("SELECT p.id FROM ".
|
||||
Person::class." p ".
|
||||
" WHERE p.center = :center")
|
||||
$personIds = $qb
|
||||
->select('p.id')
|
||||
->distinct(true)
|
||||
->from(Person::class, 'p')
|
||||
->join('p.accompanyingPeriodParticipations', 'participation')
|
||||
->join('participation.accompanyingPeriod', 'ap')
|
||||
->where(
|
||||
$qb->expr()->eq(
|
||||
'p.center',
|
||||
':center'
|
||||
)
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->gt(
|
||||
'SIZE(p.accompanyingPeriodParticipations)',
|
||||
0)
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->eq('ap.step', ':step')
|
||||
)
|
||||
->setParameter('center', $center)
|
||||
->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED)
|
||||
->setMaxResults($maxResults)
|
||||
->getQuery()
|
||||
->getScalarResult();
|
||||
|
||||
// create a random order
|
||||
@@ -400,11 +424,11 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
*
|
||||
* @dataProvider dataGenerateRandomAccompanyingCourse
|
||||
*/
|
||||
public function testAccompanyingCourseAddParticipation(int $personId, AccompanyingPeriod $period)
|
||||
public function testAccompanyingCourseAddParticipation(int $personId, int $periodId)
|
||||
{
|
||||
$this->client->request(
|
||||
Request::METHOD_POST,
|
||||
sprintf('/api/1.0/person/accompanying-course/%d/participation.json', $period->getId()),
|
||||
sprintf('/api/1.0/person/accompanying-course/%d/participation.json', $periodId),
|
||||
[], // parameters
|
||||
[], // files
|
||||
[], // server parameters
|
||||
@@ -420,7 +444,8 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
|
||||
// check by deownloading the accompanying cours
|
||||
|
||||
$this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', $period->getId()));
|
||||
$this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', $periodId));
|
||||
$this->assertEquals(200, $response->getStatusCode(), "Test that the response of rest api has a status code ok (200)");
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$data = \json_decode($response->getContent());
|
||||
@@ -435,7 +460,7 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
// check removing the participation
|
||||
$this->client->request(
|
||||
Request::METHOD_DELETE,
|
||||
sprintf('/api/1.0/person/accompanying-course/%d/participation.json', $period->getId()),
|
||||
sprintf('/api/1.0/person/accompanying-course/%d/participation.json', $periodId),
|
||||
[], // parameters
|
||||
[], // files
|
||||
[], // server parameters
|
||||
@@ -450,42 +475,6 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
$this->assertNotNull($data['startDate']);
|
||||
$this->assertArrayHasKey('endDate', $data);
|
||||
$this->assertNotNull($data['endDate']);
|
||||
|
||||
|
||||
// set to variable for tear down
|
||||
$this->personId = $personId;
|
||||
$this->period = $period;
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$em = static::$container->get(EntityManagerInterface::class);
|
||||
|
||||
// remove participation if set
|
||||
if ($this->personId && $this->period) {
|
||||
$participation = $em
|
||||
->getRepository(AccompanyingPeriodParticipation::class)
|
||||
->findOneBy(['person' => $this->personId, 'accompanyingPeriod' => $this->period])
|
||||
;
|
||||
|
||||
if (NULL !== $participation) {
|
||||
$em->remove($participation);
|
||||
$em->flush();
|
||||
}
|
||||
$this->personId = NULL;
|
||||
$this->period = NULL;
|
||||
} elseif ($this->period) {
|
||||
$period = $em
|
||||
->getRepository(AccompanyingPeriod::class)
|
||||
->find($this->period->getId()) ;
|
||||
|
||||
if ($period !== NULL) {
|
||||
$em->remove($period);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
$this->period = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function dataGenerateRandomAccompanyingCourseWithSocialIssue()
|
||||
@@ -505,12 +494,32 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
$em = static::$container->get(EntityManagerInterface::class);
|
||||
$center = $em->getRepository(Center::class)
|
||||
->findOneBy(array('name' => 'Center A'));
|
||||
$qb = $em->createQueryBuilder();
|
||||
|
||||
$personIds = $em->createQuery("SELECT p.id FROM ".
|
||||
Person::class." p ".
|
||||
" WHERE p.center = :center")
|
||||
$personIds = $qb
|
||||
->select('p.id')
|
||||
->distinct(true)
|
||||
->from(Person::class, 'p')
|
||||
->join('p.accompanyingPeriodParticipations', 'participation')
|
||||
->join('participation.accompanyingPeriod', 'ap')
|
||||
->where(
|
||||
$qb->expr()->eq(
|
||||
'p.center',
|
||||
':center'
|
||||
)
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->gt(
|
||||
'SIZE(p.accompanyingPeriodParticipations)',
|
||||
0)
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->eq('ap.step', ':step')
|
||||
)
|
||||
->setParameter('center', $center)
|
||||
->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED)
|
||||
->setMaxResults($maxResults)
|
||||
->getQuery()
|
||||
->getScalarResult();
|
||||
|
||||
// create a random order
|
||||
@@ -556,7 +565,10 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
$qb = $em->createQueryBuilder();
|
||||
$personIds = $qb
|
||||
->select('p.id')
|
||||
->distinct(true)
|
||||
->from(Person::class, 'p')
|
||||
->join('p.accompanyingPeriodParticipations', 'participation')
|
||||
->join('participation.accompanyingPeriod', 'ap')
|
||||
->where(
|
||||
$qb->expr()->eq(
|
||||
'p.center',
|
||||
@@ -568,7 +580,11 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
'SIZE(p.accompanyingPeriodParticipations)',
|
||||
0)
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->eq('ap.step', ':step')
|
||||
)
|
||||
->setParameter('center', $center)
|
||||
->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED)
|
||||
->setMaxResults($maxResults)
|
||||
->getQuery()
|
||||
->getScalarResult();
|
||||
@@ -584,7 +600,7 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
|
||||
->find($id);
|
||||
$periods = $person->getAccompanyingPeriods();
|
||||
|
||||
yield [\array_pop($personIds)["id"], $periods[\array_rand($periods)] ];
|
||||
yield [\array_pop($personIds)["id"], $periods[\array_rand($periods)]->getId() ];
|
||||
|
||||
$nbGenerated++;
|
||||
}
|
||||
|
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Julien Fastré <julien.fastre@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\Tests\Validator;
|
||||
|
||||
use Chill\PersonBundle\Validator\Constraints\BirthdateValidator;
|
||||
use Chill\PersonBundle\Validator\Constraints\Birthdate;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophet;
|
||||
|
||||
/**
|
||||
* Test the behaviour of BirthdayValidator
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class BirthdateValidatorTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* A prophecy for \Symfony\Component\Validator\Context\ExecutionContextInterface
|
||||
*
|
||||
* Will reveal \Symfony\Component\Validator\Context\ExecutionContextInterface
|
||||
*/
|
||||
private $context;
|
||||
|
||||
private $prophet;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var Birthdate
|
||||
*/
|
||||
private $constraint;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->prophet = new Prophet;
|
||||
|
||||
$constraintViolationBuilder = $this->prophet->prophesize();
|
||||
$constraintViolationBuilder->willImplement('Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface');
|
||||
|
||||
$constraintViolationBuilder->setParameter(Argument::any(), Argument::any())
|
||||
->willReturn($constraintViolationBuilder->reveal());
|
||||
$constraintViolationBuilder->addViolation()
|
||||
->willReturn($constraintViolationBuilder->reveal());
|
||||
|
||||
$this->context = $this->prophet->prophesize();
|
||||
$this->context->willImplement('Symfony\Component\Validator\Context\ExecutionContextInterface');
|
||||
$this->context->buildViolation(Argument::type('string'))
|
||||
->willReturn($constraintViolationBuilder->reveal());
|
||||
|
||||
$this->constraint = new Birthdate();
|
||||
}
|
||||
|
||||
public function testValidBirthDate()
|
||||
{
|
||||
|
||||
$date = new \DateTime('2015-01-01');
|
||||
|
||||
$birthdateValidator = new BirthdateValidator();
|
||||
$birthdateValidator->initialize($this->context->reveal());
|
||||
|
||||
$birthdateValidator->validate($date, $this->constraint);
|
||||
|
||||
$this->context->buildViolation(Argument::any())->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
public function testInvalidBirthDate()
|
||||
{
|
||||
$date = new \DateTime('tomorrow');
|
||||
|
||||
$birthdateValidator = new BirthdateValidator();
|
||||
$birthdateValidator->initialize($this->context->reveal());
|
||||
|
||||
$birthdateValidator->validate($date, $this->constraint);
|
||||
|
||||
$this->context->buildViolation(Argument::type('string'))->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
public function testInvalidBirthDateWithParameter()
|
||||
{
|
||||
$date = (new \DateTime('today'))->sub(new \DateInterval('P1M'));
|
||||
|
||||
$birthdateValidator = new BirthdateValidator('P1Y');
|
||||
$birthdateValidator->initialize($this->context->reveal());
|
||||
|
||||
$birthdateValidator->validate($date, $this->constraint);
|
||||
|
||||
$this->context->buildViolation(Argument::type('string'))->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
$this->prophet->checkPredictions();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Julien Fastré <julien.fastre@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\Tests\Validator\Person;
|
||||
|
||||
use Chill\PersonBundle\Validator\Constraints\Person\BirthdateValidator;
|
||||
use Chill\PersonBundle\Validator\Constraints\Person\Birthdate;
|
||||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
/**
|
||||
* Test the behaviour of BirthdayValidator
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class BirthdateValidatorTest extends ConstraintValidatorTestCase
|
||||
{
|
||||
|
||||
public function testValidateTodayInvalid()
|
||||
{
|
||||
$bornToday = new \DateTime('now');
|
||||
$this->validator->validate($bornToday, $this->createConstraint());
|
||||
$this->buildViolation('msg')
|
||||
->setParameter('%date%', (new \DateTime('yesterday'))->format('d-m-Y'))
|
||||
->setCode('3f42fd96-0b2d-11ec-8cf3-0f3b1b1ca1c4')
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public function testValidateYesterdayValid()
|
||||
{
|
||||
$bornYesterday = new \DateTime('yesterday');
|
||||
$this->validator->validate($bornYesterday, $this->createConstraint());
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function testTomorrowInvalid()
|
||||
{
|
||||
$bornAfter = new \DateTime('+2 days');
|
||||
$this->validator->validate($bornAfter, $this->createConstraint());
|
||||
$this->buildViolation('msg')
|
||||
->setParameter('%date%', (new \DateTime('yesterday'))->format('d-m-Y'))
|
||||
->setCode('3f42fd96-0b2d-11ec-8cf3-0f3b1b1ca1c4')
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
private function createConstraint()
|
||||
{
|
||||
return new Birthdate([
|
||||
'message' => 'msg'
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
protected function createValidator()
|
||||
{
|
||||
return new BirthdateValidator('P1D');
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Validator\Person;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Validator\Constraints\Person\PersonHasCenter;
|
||||
use Chill\PersonBundle\Validator\Constraints\Person\PersonHasCenterValidator;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
|
||||
class PersonHasCenterValidatorTest extends \Symfony\Component\Validator\Test\ConstraintValidatorTestCase
|
||||
{
|
||||
public function testValidateRequired()
|
||||
{
|
||||
$constraint = $this->getConstraint();
|
||||
$personHasCenter = (new Person())->setCenter(new Center());
|
||||
$personNoCenter = new Person();
|
||||
|
||||
$this->validator->validate($personHasCenter, $constraint);
|
||||
$this->assertNoViolation();
|
||||
|
||||
$this->validator->validate($personNoCenter, $constraint);
|
||||
$this->buildViolation('msg')
|
||||
->atPath('property.path.center')
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
protected function getConstraint()
|
||||
{
|
||||
return new PersonHasCenter([
|
||||
'message' => 'msg'
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createValidator()
|
||||
{
|
||||
$parameterBag = $this->createMock(ParameterBagInterface::class);
|
||||
$parameterBag
|
||||
->method('get')
|
||||
->with($this->equalTo('chill_person'))
|
||||
->willReturn([
|
||||
'validation' => [
|
||||
'center_required' => true
|
||||
]
|
||||
])
|
||||
;
|
||||
|
||||
return new PersonHasCenterValidator($parameterBag);
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Validator\Person;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Validator\Constraints\Person\Birthdate;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
class PersonValidationTest extends KernelTestCase
|
||||
{
|
||||
private ValidatorInterface $validator;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->validator = self::$container->get(ValidatorInterface::class);
|
||||
}
|
||||
|
||||
public function testFirstnameValidation()
|
||||
{
|
||||
$person = (new Person())
|
||||
->setFirstname(\str_repeat('a', 500));
|
||||
$errors = $this->validator->validate($person, null, ["creation"]);
|
||||
foreach ($errors->getIterator() as $error) {
|
||||
if (Length::TOO_LONG_ERROR === $error->getCode()) {
|
||||
$this->assertTrue(true,
|
||||
"error code for firstname too long is present");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->assertTrue(false,
|
||||
"error code for fistname too long is present");
|
||||
|
||||
}
|
||||
|
||||
public function testBirthdateInFuture()
|
||||
{
|
||||
$person = (new Person())
|
||||
->setBirthdate(new \Datetime('+2 months'));
|
||||
$errors = $this->validator->validate($person, null, ["creation"]);
|
||||
foreach ($errors->getIterator() as $error) {
|
||||
if (Birthdate::BIRTHDATE_INVALID_CODE === $error->getCode()) {
|
||||
$this->assertTrue(true,
|
||||
"error code for birthdate invalid is present");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->assertTrue(false,
|
||||
"error code for birthdate invalid is present");
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -17,26 +17,23 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Chill\PersonBundle\Validator\Constraints;
|
||||
namespace Chill\PersonBundle\Validator\Constraints\Person;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Create a constraint on birth date: the birthdate after today are not allowed.
|
||||
*
|
||||
* It is possible to add a delay before today, expressed as described in
|
||||
*
|
||||
* It is possible to add a delay before today, expressed as described in
|
||||
* interval_spec : http://php.net/manual/en/dateinterval.construct.php
|
||||
* (this interval_spec itself is based on ISO8601 :
|
||||
* https://en.wikipedia.org/wiki/ISO_8601#Durations)
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
* @Annotation
|
||||
*/
|
||||
class Birthdate extends Constraint
|
||||
{
|
||||
public const BIRTHDATE_INVALID_CODE = '3f42fd96-0b2d-11ec-8cf3-0f3b1b1ca1c4';
|
||||
|
||||
public $message = "The birthdate must be before %date%";
|
||||
|
||||
public function validatedBy()
|
||||
{
|
||||
return 'birthdate_not_before';
|
||||
}
|
||||
}
|
@@ -17,49 +17,50 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Chill\PersonBundle\Validator\Constraints;
|
||||
namespace Chill\PersonBundle\Validator\Constraints\Person;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class BirthdateValidator extends ConstraintValidator
|
||||
{
|
||||
private $interval_spec = null;
|
||||
|
||||
|
||||
public function __construct($interval_spec = null)
|
||||
{
|
||||
$this->interval_spec = $interval_spec;
|
||||
}
|
||||
|
||||
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if ($value === NULL) {
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!$value instanceof \DateTime) {
|
||||
throw new \LogicException('The input should a be a \DateTime interface,'
|
||||
. (is_object($value) ? get_class($value) : gettype($value)));
|
||||
}
|
||||
|
||||
|
||||
$limitDate = $this->getLimitDate();
|
||||
|
||||
|
||||
if ($limitDate < $value) {
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->setParameter('%date%', $limitDate->format('d-m-Y'))
|
||||
->setCode(Birthdate::BIRTHDATE_INVALID_CODE)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
private function getLimitDate()
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Validator\Constraints\Person;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
*/
|
||||
class PersonHasCenter extends \Symfony\Component\Validator\Constraint
|
||||
{
|
||||
public string $message = "A center is required";
|
||||
|
||||
public function getTargets()
|
||||
{
|
||||
return [
|
||||
self::CLASS_CONSTRAINT
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Validator\Constraints\Person;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
class PersonHasCenterValidator extends \Symfony\Component\Validator\ConstraintValidator
|
||||
{
|
||||
private bool $centerRequired;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameterBag)
|
||||
{
|
||||
$this->centerRequired = $parameterBag->get('chill_person')['validation']['center_required'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate($person, Constraint $constraint)
|
||||
{
|
||||
if (!$person instanceof Person) {
|
||||
throw new UnexpectedTypeException($constraint, Person::class);
|
||||
}
|
||||
|
||||
if (!$this->centerRequired) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NULL === $person->getCenter()) {
|
||||
$this
|
||||
->context
|
||||
->buildViolation($constraint->message)
|
||||
->atPath('center')
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
@@ -45,7 +45,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'person'
|
||||
- "person"
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
@@ -55,7 +55,9 @@ components:
|
||||
description: a canonical representation for the person name
|
||||
readOnly: true
|
||||
birthdate:
|
||||
$ref: '#/components/schemas/Date'
|
||||
$ref: "#/components/schemas/Date"
|
||||
deathdate:
|
||||
$ref: "#/components/schemas/Date"
|
||||
phonenumber:
|
||||
type: string
|
||||
mobilenumber:
|
||||
@@ -78,7 +80,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'person'
|
||||
- "person"
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
@@ -96,7 +98,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'thirdparty'
|
||||
- "thirdparty"
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
@@ -119,15 +121,15 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_resource'
|
||||
- "accompanying_period_resource"
|
||||
readOnly: true
|
||||
id:
|
||||
type: integer
|
||||
readOnly: true
|
||||
resource:
|
||||
anyOf:
|
||||
- $ref: '#/components/schemas/PersonById'
|
||||
- $ref: '#/components/schemas/ThirdPartyById'
|
||||
- $ref: "#/components/schemas/PersonById"
|
||||
- $ref: "#/components/schemas/ThirdPartyById"
|
||||
ResourceById:
|
||||
type: object
|
||||
properties:
|
||||
@@ -136,7 +138,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_resource'
|
||||
- "accompanying_period_resource"
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
@@ -146,7 +148,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_comment'
|
||||
- "accompanying_period_comment"
|
||||
readOnly: true
|
||||
id:
|
||||
type: integer
|
||||
@@ -161,7 +163,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_comment'
|
||||
- "accompanying_period_comment"
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
@@ -173,7 +175,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'social_issue'
|
||||
- "social_issue"
|
||||
parent_id:
|
||||
type: integer
|
||||
readOnly: true
|
||||
@@ -195,12 +197,12 @@ components:
|
||||
Household:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'household'
|
||||
id:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- "household"
|
||||
HouseholdPosition:
|
||||
type: object
|
||||
properties:
|
||||
@@ -209,7 +211,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'household_position'
|
||||
- "household_position"
|
||||
AccompanyingCourseWork:
|
||||
type: object
|
||||
properties:
|
||||
@@ -218,7 +220,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_work'
|
||||
- "accompanying_period_work"
|
||||
note:
|
||||
type: string
|
||||
startDate:
|
||||
@@ -243,15 +245,15 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_work_goal'
|
||||
- "accompanying_period_work_goal"
|
||||
note:
|
||||
type: string
|
||||
goal:
|
||||
$ref: '#/components/schemas/SocialWorkGoalById'
|
||||
$ref: "#/components/schemas/SocialWorkGoalById"
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SocialWorkGoalById'
|
||||
$ref: "#/components/schemas/SocialWorkGoalById"
|
||||
|
||||
SocialWorkResultById:
|
||||
type: object
|
||||
@@ -261,7 +263,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'social_work_result'
|
||||
- "social_work_result"
|
||||
SocialWorkGoalById:
|
||||
type: object
|
||||
properties:
|
||||
@@ -270,7 +272,7 @@ components:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'social_work_goal'
|
||||
- "social_work_goal"
|
||||
|
||||
paths:
|
||||
/1.0/person/person/{id}.json:
|
||||
@@ -308,7 +310,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Person'
|
||||
$ref: "#/components/schemas/Person"
|
||||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
@@ -355,18 +357,17 @@ paths:
|
||||
422:
|
||||
description: "Unprocessable entity (validation errors)"
|
||||
|
||||
|
||||
/1.0/person/address/suggest/by-person/{id}.json:
|
||||
get:
|
||||
tags:
|
||||
- address
|
||||
summary: get a list of suggested address for a person
|
||||
description: >
|
||||
The address are computed from various source. Currently:
|
||||
The address are computed from various source. Currently:
|
||||
|
||||
- the address of course to which the person is participating
|
||||
- the address of course to which the person is participating
|
||||
|
||||
The current person's address is always ignored.
|
||||
The current person's address is always ignored.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
@@ -390,11 +391,11 @@ paths:
|
||||
- address
|
||||
summary: get a list of suggested address for a household
|
||||
description: >
|
||||
The address are computed from various source. Currently:
|
||||
The address are computed from various source. Currently:
|
||||
|
||||
- the address of course to which the members is participating
|
||||
- the address of course to which the members is participating
|
||||
|
||||
The current household address is always ignored.
|
||||
The current household address is always ignored.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
@@ -452,7 +453,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AccompanyingPeriod'
|
||||
$ref: "#/components/schemas/AccompanyingPeriod"
|
||||
examples:
|
||||
Set the requestor as anonymous:
|
||||
value:
|
||||
@@ -488,7 +489,7 @@ paths:
|
||||
id: 0
|
||||
personLocation: null
|
||||
addressLocation:
|
||||
id: 7960
|
||||
id: 7960
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@@ -520,8 +521,8 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/PersonById'
|
||||
- $ref: '#/components/schemas/ThirdPartyById'
|
||||
- $ref: "#/components/schemas/PersonById"
|
||||
- $ref: "#/components/schemas/ThirdPartyById"
|
||||
examples:
|
||||
add person with id 50:
|
||||
summary: "a person with id 50"
|
||||
@@ -585,7 +586,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PersonById'
|
||||
$ref: "#/components/schemas/PersonById"
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@@ -614,7 +615,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PersonById'
|
||||
$ref: "#/components/schemas/PersonById"
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@@ -645,7 +646,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Resource'
|
||||
$ref: "#/components/schemas/Resource"
|
||||
examples:
|
||||
add person with id 50:
|
||||
summary: "a person with id 50"
|
||||
@@ -690,7 +691,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ResourceById'
|
||||
$ref: "#/components/schemas/ResourceById"
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@@ -721,7 +722,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Comment'
|
||||
$ref: "#/components/schemas/Comment"
|
||||
examples:
|
||||
a single comment:
|
||||
summary: "a simple comment"
|
||||
@@ -759,7 +760,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CommentById'
|
||||
$ref: "#/components/schemas/CommentById"
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@@ -790,7 +791,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Scope'
|
||||
$ref: "#/components/schemas/Scope"
|
||||
examples:
|
||||
add a scope:
|
||||
value:
|
||||
@@ -824,7 +825,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ScopeById'
|
||||
$ref: "#/components/schemas/ScopeById"
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@@ -855,7 +856,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SocialIssue'
|
||||
$ref: "#/components/schemas/SocialIssue"
|
||||
examples:
|
||||
add a social issue:
|
||||
value:
|
||||
@@ -889,7 +890,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SocialIssue'
|
||||
$ref: "#/components/schemas/SocialIssue"
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@@ -926,11 +927,11 @@ paths:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_work'
|
||||
- "accompanying_period_work"
|
||||
startDate:
|
||||
$ref: '#/components/schemas/Date'
|
||||
$ref: "#/components/schemas/Date"
|
||||
endDate:
|
||||
$ref: '#/components/schemas/Date'
|
||||
$ref: "#/components/schemas/Date"
|
||||
examples:
|
||||
create a work:
|
||||
value:
|
||||
@@ -992,7 +993,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AccompanyingCourseWork'
|
||||
$ref: "#/components/schemas/AccompanyingCourseWork"
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@@ -1029,8 +1030,6 @@ paths:
|
||||
400:
|
||||
description: "transition cannot be applyed"
|
||||
|
||||
|
||||
|
||||
/1.0/person/accompanying-period/origin.json:
|
||||
get:
|
||||
tags:
|
||||
@@ -1064,8 +1063,6 @@ paths:
|
||||
404:
|
||||
description: "Not found"
|
||||
|
||||
|
||||
|
||||
/1.0/person/household.json:
|
||||
get:
|
||||
tags:
|
||||
@@ -1095,7 +1092,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Household'
|
||||
$ref: "#/components/schemas/Household"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
@@ -1107,9 +1104,9 @@ paths:
|
||||
- household
|
||||
summary: Return households associated with the given person through accompanying periods
|
||||
description: |
|
||||
Return households associated with the given person throught accompanying periods participation.
|
||||
Return households associated with the given person throught accompanying periods participation.
|
||||
|
||||
The current household of the given person is excluded.
|
||||
The current household of the given person is excluded.
|
||||
parameters:
|
||||
- name: person_id
|
||||
in: path
|
||||
@@ -1125,7 +1122,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Household'
|
||||
$ref: "#/components/schemas/Household"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
@@ -1149,23 +1146,22 @@ paths:
|
||||
type: object
|
||||
properties:
|
||||
person:
|
||||
$ref: '#/components/schemas/PersonById'
|
||||
$ref: "#/components/schemas/PersonById"
|
||||
start_date:
|
||||
$ref: '#/components/schemas/Date'
|
||||
$ref: "#/components/schemas/Date"
|
||||
position:
|
||||
$ref: '#/components/schemas/HouseholdPosition'
|
||||
$ref: "#/components/schemas/HouseholdPosition"
|
||||
holder:
|
||||
type: boolean
|
||||
comment:
|
||||
type: string
|
||||
destination:
|
||||
$ref: '#/components/schemas/Household'
|
||||
$ref: "#/components/schemas/Household"
|
||||
examples:
|
||||
Moving person to a new household:
|
||||
value:
|
||||
concerned:
|
||||
-
|
||||
person:
|
||||
- person:
|
||||
id: 0
|
||||
type: person
|
||||
position:
|
||||
@@ -1180,8 +1176,7 @@ paths:
|
||||
Moving person to a new household and set an address to this household:
|
||||
value:
|
||||
concerned:
|
||||
-
|
||||
person:
|
||||
- person:
|
||||
id: 0
|
||||
type: person
|
||||
position:
|
||||
@@ -1198,8 +1193,7 @@ paths:
|
||||
Moving person to an existing household:
|
||||
value:
|
||||
concerned:
|
||||
-
|
||||
person:
|
||||
- person:
|
||||
id: 0
|
||||
type: person
|
||||
position:
|
||||
@@ -1215,8 +1209,7 @@ paths:
|
||||
Removing a person from any household:
|
||||
value:
|
||||
concerned:
|
||||
-
|
||||
person:
|
||||
- person:
|
||||
id: 0
|
||||
type: person
|
||||
start_date:
|
||||
@@ -1270,8 +1263,6 @@ paths:
|
||||
400:
|
||||
description: "transition cannot be applyed"
|
||||
|
||||
|
||||
|
||||
/1.0/person/social/social-action.json:
|
||||
get:
|
||||
tags:
|
||||
@@ -1349,7 +1340,6 @@ paths:
|
||||
404:
|
||||
description: not found
|
||||
|
||||
|
||||
/1.0/person/social-work/social-issue.json:
|
||||
get:
|
||||
tags:
|
||||
@@ -1379,7 +1369,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SocialIssue'
|
||||
$ref: "#/components/schemas/SocialIssue"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
|
@@ -24,14 +24,6 @@ services:
|
||||
tags:
|
||||
- { name: console.command }
|
||||
|
||||
chill.person.form.type.select2maritalstatus:
|
||||
class: Chill\PersonBundle\Form\Type\Select2MaritalStatusType
|
||||
arguments:
|
||||
- "@request_stack"
|
||||
- "@doctrine.orm.entity_manager"
|
||||
tags:
|
||||
- { name: form.type, alias: select2_chill_marital_status }
|
||||
|
||||
chill.person.timeline.accompanying_period_opening:
|
||||
class: Chill\PersonBundle\Timeline\TimelineAccompanyingPeriodOpening
|
||||
arguments:
|
||||
@@ -62,23 +54,22 @@ services:
|
||||
- { name: security.voter }
|
||||
- { name: chill.role }
|
||||
|
||||
chill.person.birthdate_validation:
|
||||
class: Chill\PersonBundle\Validator\Constraints\BirthdateValidator
|
||||
Chill\PersonBundle\Validator\Constraints\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../Validator/Constraints/'
|
||||
|
||||
# override default config, must be loaded after resource
|
||||
Chill\PersonBundle\Validator\Constraints\BirthdateValidator:
|
||||
arguments:
|
||||
- "%chill_person.validation.birtdate_not_before%"
|
||||
tags:
|
||||
- { name: validator.constraint_validator, alias: birthdate_not_before }
|
||||
|
||||
Chill\PersonBundle\Validator\Constraints\Household\HouseholdMembershipSequentialValidator:
|
||||
autowire: true
|
||||
tags:
|
||||
- { name: validator.constraint_validator }
|
||||
|
||||
Chill\PersonBundle\Repository\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../Repository/'
|
||||
tags: ['doctrine.repository_service']
|
||||
|
||||
Chill\PersonBundle\Controller\:
|
||||
autowire: true
|
||||
|
@@ -12,9 +12,8 @@ services:
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
Chill\PersonBundle\Controller\AccompanyingPeriodController:
|
||||
arguments:
|
||||
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
|
||||
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
Chill\PersonBundle\Controller\PersonAddressController:
|
||||
|
@@ -1,5 +1,10 @@
|
||||
services:
|
||||
|
||||
Chill\PersonBundle\Form\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../../Form/'
|
||||
|
||||
Chill\PersonBundle\Form\PersonType:
|
||||
arguments:
|
||||
- '%chill_person.person_fields%'
|
||||
@@ -7,14 +12,7 @@ services:
|
||||
tags:
|
||||
- { name: form.type, alias: '@chill.person.form.person_creation' }
|
||||
|
||||
Chill\PersonBundle\Form\CreationPersonType:
|
||||
arguments:
|
||||
- '@chill.main.form.data_transformer.center_transformer'
|
||||
- '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper'
|
||||
tags:
|
||||
- { name: form.type, alias: '@chill.main.form.person_creation' }
|
||||
|
||||
chill.person.accompanying_period_closing_motive:
|
||||
Chill\PersonBundle\Form\Type\ClosingMotivePickerType:
|
||||
class: Chill\PersonBundle\Form\Type\ClosingMotivePickerType
|
||||
arguments:
|
||||
$translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
|
||||
@@ -28,60 +26,3 @@ services:
|
||||
$config: "%chill_person.accompanying_period_fields%"
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
chill.person.form.type.pick_person:
|
||||
class: Chill\PersonBundle\Form\Type\PickPersonType
|
||||
arguments:
|
||||
- '@Chill\PersonBundle\Repository\PersonRepository'
|
||||
- "@security.token_storage"
|
||||
- "@chill.main.security.authorization.helper"
|
||||
- '@Symfony\Component\Routing\Generator\UrlGeneratorInterface'
|
||||
- '@Symfony\Component\Translation\TranslatorInterface'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\PersonBundle\Form\Type\PersonAltNameType:
|
||||
arguments:
|
||||
$configHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper'
|
||||
$translatableStringHelper: '@chill.main.helper.translatable_string'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\PersonBundle\Form\Type\PersonPhoneType:
|
||||
arguments:
|
||||
$phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper'
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\PersonBundle\Form\SocialWork\SocialIssueType:
|
||||
arguments:
|
||||
$translatableStringHelper: '@chill.main.helper.translatable_string'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\PersonBundle\Form\SocialWork\SocialActionType:
|
||||
arguments:
|
||||
$translatableStringHelper: '@chill.main.helper.translatable_string'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\PersonBundle\Form\SocialWork\EvaluationType:
|
||||
arguments:
|
||||
$translatableStringHelper: '@chill.main.helper.translatable_string'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
|
||||
Chill\PersonBundle\Form\SocialWork\GoalType:
|
||||
arguments:
|
||||
$translatableStringHelper: '@chill.main.helper.translatable_string'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
|
||||
Chill\PersonBundle\Form\SocialWork\ResultType:
|
||||
arguments:
|
||||
$translatableStringHelper: '@chill.main.helper.translatable_string'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
@@ -19,14 +19,7 @@ services:
|
||||
# - { name: 'chill.menu_builder' }
|
||||
#
|
||||
Chill\PersonBundle\Menu\PersonMenuBuilder:
|
||||
arguments:
|
||||
$showAccompanyingPeriod: '%chill_person.accompanying_period%'
|
||||
$translator: '@Symfony\Contracts\Translation\TranslatorInterface'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
tags:
|
||||
- { name: 'chill.menu_builder' }
|
||||
|
||||
# Chill\PersonBundle\Menu\AccompanyingCourseMenuBuilder:
|
||||
# arguments:
|
||||
# $translator: '@Symfony\Contracts\Translation\TranslatorInterface'
|
||||
# tags:
|
||||
# - { name: 'chill.menu_builder' }
|
||||
|
@@ -1,6 +1,12 @@
|
||||
services:
|
||||
# note: the services.yaml file define some autoloading
|
||||
|
||||
chill.person.repository.person:
|
||||
class: Chill\PersonBundle\Repository\PersonRepository
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
Chill\PersonBundle\Repository\PersonRepository: '@chill.person.repository.person'
|
||||
|
||||
Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface: '@Chill\PersonBundle\Repository\PersonACLAwareRepository'
|
||||
|
||||
Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface: '@Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository'
|
||||
|
@@ -1,25 +1,11 @@
|
||||
services:
|
||||
chill.person.search_person:
|
||||
class: Chill\PersonBundle\Search\PersonSearch
|
||||
arguments:
|
||||
- "@doctrine.orm.entity_manager"
|
||||
- "@security.token_storage"
|
||||
- "@chill.main.security.authorization.helper"
|
||||
- "@chill_main.paginator_factory"
|
||||
calls:
|
||||
- ['setContainer', ["@service_container"]]
|
||||
Chill\PersonBundle\Search\PersonSearch:
|
||||
autowire: true
|
||||
tags:
|
||||
- { name: chill.search, alias: 'person_regular' }
|
||||
|
||||
Chill\PersonBundle\Search\SimilarityPersonSearch:
|
||||
arguments:
|
||||
- "@doctrine.orm.entity_manager"
|
||||
- "@security.token_storage"
|
||||
- "@chill.main.security.authorization.helper"
|
||||
- "@chill_main.paginator_factory"
|
||||
- '@chill.person.search_person'
|
||||
calls:
|
||||
- ['setContainer', ["@service_container"]]
|
||||
autowire: true
|
||||
tags:
|
||||
- { name: chill.search, alias: 'person_similarity' }
|
||||
|
||||
|
@@ -1,16 +1,14 @@
|
||||
services:
|
||||
chill.person.security.authorization.person:
|
||||
autowire: true
|
||||
class: Chill\PersonBundle\Security\Authorization\PersonVoter
|
||||
arguments:
|
||||
- "@chill.main.security.authorization.helper"
|
||||
tags:
|
||||
- { name: security.voter }
|
||||
- { name: chill.role }
|
||||
|
||||
Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter:
|
||||
arguments:
|
||||
- "@chill.main.security.authorization.helper"
|
||||
autowire: true
|
||||
tags:
|
||||
- { name: security.voter }
|
||||
- { name: chill.role }
|
||||
|
||||
|
||||
|
@@ -1,70 +1,3 @@
|
||||
Chill\PersonBundle\Entity\Person:
|
||||
properties:
|
||||
firstName:
|
||||
- NotBlank:
|
||||
groups: [general, creation]
|
||||
- Length:
|
||||
min: 2
|
||||
max: 255
|
||||
minMessage: 'This name is too short. It must containt {{ limit }} chars'
|
||||
maxMessage: 'This name is too long. It must containt {{ limit }} chars'
|
||||
groups: [general, creation]
|
||||
lastName:
|
||||
- NotBlank:
|
||||
groups: [general, creation]
|
||||
- Length:
|
||||
min: 2
|
||||
max: 255
|
||||
minMessage: 'This name is too short. It must containt {{ limit }} chars'
|
||||
maxMessage: 'This name is too long. It must containt {{ limit }} chars'
|
||||
groups: [general, creation]
|
||||
birthdate:
|
||||
- Date:
|
||||
message: 'Birthdate not valid'
|
||||
groups: [general, creation]
|
||||
- Chill\PersonBundle\Validator\Constraints\Birthdate:
|
||||
groups: [general, creation]
|
||||
gender:
|
||||
- NotNull:
|
||||
groups: [general, creation]
|
||||
#accompanyingPeriods:
|
||||
# - Valid:
|
||||
# traverse: true
|
||||
email:
|
||||
- Email:
|
||||
groups: [general, creation]
|
||||
message: 'The email is not valid'
|
||||
checkMX: true
|
||||
phonenumber:
|
||||
- Regex:
|
||||
pattern: '/^([\+{1}])([0-9\s*]{4,20})$/'
|
||||
groups: [general, creation]
|
||||
message: 'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33123456789'
|
||||
- Chill\MainBundle\Validation\Constraint\PhonenumberConstraint:
|
||||
type: landline
|
||||
groups: [ general, creation ]
|
||||
mobilenumber:
|
||||
- Regex:
|
||||
pattern: '/^([\+{1}])([0-9\s*]{4,20})$/'
|
||||
groups: [general, creation]
|
||||
message: 'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33623456789'
|
||||
- Chill\MainBundle\Validation\Constraint\PhonenumberConstraint:
|
||||
type: mobile
|
||||
groups: [ general, creation ]
|
||||
otherPhoneNumbers:
|
||||
- Valid:
|
||||
traverse: true
|
||||
constraints:
|
||||
- Callback:
|
||||
callback: isAccompanyingPeriodValid
|
||||
groups: [accompanying_period_consistent]
|
||||
- Callback:
|
||||
callback: isAddressesValid
|
||||
groups: [addresses_consistent]
|
||||
- Chill\PersonBundle\Validator\Constraints\Household\HouseholdMembershipSequential:
|
||||
groups: [ 'household_memberships' ]
|
||||
|
||||
|
||||
Chill\PersonBundle\Entity\AccompanyingPeriod:
|
||||
properties:
|
||||
openingDate:
|
||||
|
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Person;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Allow to create persons without center
|
||||
*/
|
||||
final class Version20210831140339 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Allow to create persons without center';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_person_person ALTER center_id DROP NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_person_person ALTER center_id SET NOT NULL');
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Person;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Optimize trigram index on person fullname: create index for both center_id and fullname
|
||||
*/
|
||||
final class Version20210910161858 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Optimize trigram index on person fullname: create index for both center_id and fullname';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP INDEX fullnamecanonical_trgm_idx');
|
||||
$this->addSql('CREATE INDEX fullnameCanonical_trgm_idx ON chill_person_person USING GIST (center_id, fullnameCanonical gist_trgm_ops)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP INDEX fullnamecanonical_trgm_idx');
|
||||
$this->addSql('CREATE INDEX fullnameCanonical_trgm_idx ON chill_person_person USING GIST (fullnameCanonical gist_trgm_ops)');
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user