mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Household/composition add + fixes household composition editor
This commit is contained in:
parent
2b47868d88
commit
53b3f98bba
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Doctrine\Model;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use DateTime;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
trait TrackCreationTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": NULL})
|
||||||
|
*/
|
||||||
|
private ?DateTimeImmutable $createdAt = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=User::class)
|
||||||
|
* @ORM\JoinColumn(nullable=true)
|
||||||
|
*/
|
||||||
|
private ?User $createdBy = null;
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedBy(): ?User
|
||||||
|
{
|
||||||
|
return $this->createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(DateTimeInterface $datetime): self
|
||||||
|
{
|
||||||
|
$this->createdAt = $datetime instanceof DateTime ? DateTimeImmutable::createFromMutable($datetime) : $datetime;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedBy(User $user): self
|
||||||
|
{
|
||||||
|
$this->createdBy = $user;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Doctrine\Model;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use DateTime;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
trait TrackUpdateTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": NULL})
|
||||||
|
*/
|
||||||
|
private ?DateTimeImmutable $updatedAt = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=User::class)
|
||||||
|
* @ORM\JoinColumn(nullable=true)
|
||||||
|
*/
|
||||||
|
private ?User $updatedBy = null;
|
||||||
|
|
||||||
|
public function getUpdatedAt(): ?DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedBy(): ?User
|
||||||
|
{
|
||||||
|
return $this->updatedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdatedAt(DateTimeInterface $datetime): self
|
||||||
|
{
|
||||||
|
$this->updatedAt = $datetime instanceof DateTime ? DateTimeImmutable::createFromMutable($datetime) : $datetime;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdatedBy(User $user): self
|
||||||
|
{
|
||||||
|
$this->updatedBy = $user;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
|
||||||
|
use Chill\PersonBundle\Form\HouseholdCompositionType;
|
||||||
|
use Chill\PersonBundle\Repository\Household\HouseholdCompositionRepository;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Form\FormFactoryInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Templating\EngineInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
class HouseholdCompositionController
|
||||||
|
{
|
||||||
|
private EngineInterface $engine;
|
||||||
|
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private FormFactoryInterface $formFactory;
|
||||||
|
|
||||||
|
private HouseholdCompositionRepository $householdCompositionRepository;
|
||||||
|
|
||||||
|
private PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
|
private UrlGeneratorInterface $urlGenerator;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Security $security,
|
||||||
|
HouseholdCompositionRepository $householdCompositionRepository,
|
||||||
|
PaginatorFactory $paginatorFactory,
|
||||||
|
FormFactoryInterface $formFactory,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
TranslatorInterface $translator,
|
||||||
|
EngineInterface $engine,
|
||||||
|
UrlGeneratorInterface $urlGenerator
|
||||||
|
) {
|
||||||
|
$this->security = $security;
|
||||||
|
$this->householdCompositionRepository = $householdCompositionRepository;
|
||||||
|
$this->paginatorFactory = $paginatorFactory;
|
||||||
|
$this->formFactory = $formFactory;
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->engine = $engine;
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{_locale}/person/household/{id}/composition/index", name="chill_person_household_composition_index")
|
||||||
|
*/
|
||||||
|
public function index(Household $household, Request $request): Response
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted(HouseholdVoter::SEE, $household)) {
|
||||||
|
throw new AccessDeniedException('not allowed to edit an household');
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = $this->householdCompositionRepository->countByHousehold($household);
|
||||||
|
$paginator = $this->paginatorFactory->create($count);
|
||||||
|
$compositions = $this->householdCompositionRepository->findByHousehold(
|
||||||
|
$household,
|
||||||
|
['startDate' => 'DESC', 'id' => 'DESC'],
|
||||||
|
$paginator->getItemsPerPage(),
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->security->isGranted(HouseholdVoter::EDIT, $household)) {
|
||||||
|
$isEdit = $request->query->has('edit');
|
||||||
|
|
||||||
|
if ($isEdit) {
|
||||||
|
$householdCompositions = $household->getCompositions()->filter(static function (HouseholdComposition $composition) use ($request) {
|
||||||
|
return $composition->getId() === $request->query->getInt('edit');
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($householdCompositions->count() !== 1) {
|
||||||
|
throw new BadRequestHttpException('could not find the composition with this id associated to the household');
|
||||||
|
}
|
||||||
|
$householdComposition = $householdCompositions->first();
|
||||||
|
} else {
|
||||||
|
$householdComposition = (new HouseholdComposition())
|
||||||
|
->setStartDate(new DateTimeImmutable());
|
||||||
|
}
|
||||||
|
$form = $this->formFactory->create(HouseholdCompositionType::class, $householdComposition);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
if (!$isEdit) {
|
||||||
|
$this->entityManager->persist($householdComposition);
|
||||||
|
$household->addComposition($householdComposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$request->getSession()->getFlashBag()->add(
|
||||||
|
'success',
|
||||||
|
$this->translator->trans('household_composition.Composition added')
|
||||||
|
);
|
||||||
|
|
||||||
|
return new RedirectResponse(
|
||||||
|
$this->urlGenerator->generate('chill_person_household_composition_index', [
|
||||||
|
'id' => $household->getId(),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && !$form->isValid()) {
|
||||||
|
$request->getSession()->getFlashBag()->add(
|
||||||
|
'warning',
|
||||||
|
$this->translator->trans('This form contains errors')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response($this->engine->render(
|
||||||
|
'@ChillPerson/HouseholdComposition/index.html.twig',
|
||||||
|
[
|
||||||
|
'household' => $household,
|
||||||
|
'compositions' => $compositions,
|
||||||
|
'form' => isset($form) ? $form->createView() : null,
|
||||||
|
'isPosted' => isset($form) ? $form->isSubmitted() : false,
|
||||||
|
'editId' => $request->query->getInt('edit', -1),
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use UnexpectedValueException;
|
||||||
|
|
||||||
|
class HouseholdCompositionTypeApiController extends ApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param QueryBuilder $query
|
||||||
|
*/
|
||||||
|
protected function customizeQuery(string $action, Request $request, $query): void
|
||||||
|
{
|
||||||
|
switch ($action) {
|
||||||
|
case '_index':
|
||||||
|
$query->andWhere($query->expr()->eq('e.active', "'TRUE'"));
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new UnexpectedValueException('unexepcted action: ' . $action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\DataFixtures\ORM;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdCompositionType;
|
||||||
|
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
||||||
|
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||||
|
use Doctrine\Persistence\ObjectManager;
|
||||||
|
|
||||||
|
class LoadHouseholdCompositionType extends AbstractFixture implements FixtureGroupInterface
|
||||||
|
{
|
||||||
|
public const TYPES = [
|
||||||
|
['fr' => 'Couple avec enfant(s)'],
|
||||||
|
['fr' => 'Couple sans enfant'],
|
||||||
|
['fr' => 'Mère seule'],
|
||||||
|
['fr' => 'Père seul'],
|
||||||
|
['fr' => 'Mère isolée'],
|
||||||
|
['fr' => 'Père isolé'],
|
||||||
|
['fr' => 'Homme seul'],
|
||||||
|
['fr' => 'Femme seule'],
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function getGroups(): array
|
||||||
|
{
|
||||||
|
return ['composition-type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load(ObjectManager $manager)
|
||||||
|
{
|
||||||
|
foreach (self::TYPES as $type) {
|
||||||
|
$manager->persist(
|
||||||
|
(new HouseholdCompositionType())
|
||||||
|
->setLabel($type)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$manager->flush();
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ namespace Chill\PersonBundle\DependencyInjection;
|
|||||||
|
|
||||||
use Chill\MainBundle\DependencyInjection\MissingBundleException;
|
use Chill\MainBundle\DependencyInjection\MissingBundleException;
|
||||||
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
|
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
|
||||||
|
use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController;
|
||||||
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
|
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
@ -760,6 +761,21 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'class' => \Chill\PersonBundle\Entity\Household\HouseholdCompositionType::class,
|
||||||
|
'name' => 'household_composition',
|
||||||
|
'base_path' => '/api/1.0/person/houehold/composition/type',
|
||||||
|
'base_role' => 'ROLE_USER',
|
||||||
|
'controller' => HouseholdCompositionTypeApiController::class,
|
||||||
|
'actions' => [
|
||||||
|
'_index' => [
|
||||||
|
'methods' => [
|
||||||
|
Request::METHOD_GET => true,
|
||||||
|
Request::METHOD_HEAD => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\Entity\Household;
|
namespace Chill\PersonBundle\Entity\Household;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
use Chill\MainBundle\Entity\Address;
|
use Chill\MainBundle\Entity\Address;
|
||||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||||
use Chill\PersonBundle\Validator\Constraints\Household\MaxHolder;
|
use Chill\PersonBundle\Validator\Constraints\Household\MaxHolder;
|
||||||
@ -23,8 +24,8 @@ use Doctrine\Common\Collections\Criteria;
|
|||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
use function count;
|
use function count;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,6 +57,18 @@ class Household
|
|||||||
*/
|
*/
|
||||||
private CommentEmbeddable $commentMembers;
|
private CommentEmbeddable $commentMembers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(
|
||||||
|
* targetEntity=HouseholdComposition::class,
|
||||||
|
* mappedBy="household",
|
||||||
|
* orphanRemoval=true,
|
||||||
|
* cascade={"persist"}
|
||||||
|
* )
|
||||||
|
* @ORM\OrderBy({"startDate": "DESC"})
|
||||||
|
* @Assert\Valid(traverse=true, groups={"household_composition"})
|
||||||
|
*/
|
||||||
|
private Collection $compositions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
* @ORM\GeneratedValue
|
* @ORM\GeneratedValue
|
||||||
@ -90,6 +103,7 @@ class Household
|
|||||||
$this->addresses = new ArrayCollection();
|
$this->addresses = new ArrayCollection();
|
||||||
$this->members = new ArrayCollection();
|
$this->members = new ArrayCollection();
|
||||||
$this->commentMembers = new CommentEmbeddable();
|
$this->commentMembers = new CommentEmbeddable();
|
||||||
|
$this->compositions = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,6 +122,18 @@ class Household
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addComposition(HouseholdComposition $composition): self
|
||||||
|
{
|
||||||
|
if (!$this->compositions->contains($composition)) {
|
||||||
|
$composition->setHousehold($this);
|
||||||
|
$this->compositions[] = $composition;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->householdCompositionConsistency();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function addMember(HouseholdMember $member): self
|
public function addMember(HouseholdMember $member): self
|
||||||
{
|
{
|
||||||
if (!$this->members->contains($member)) {
|
if (!$this->members->contains($member)) {
|
||||||
@ -136,6 +162,14 @@ class Household
|
|||||||
return $this->commentMembers;
|
return $this->commentMembers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ArrayCollection|Collection|HouseholdComposition[]
|
||||||
|
*/
|
||||||
|
public function getCompositions(): Collection
|
||||||
|
{
|
||||||
|
return $this->compositions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Serializer\Groups({"read", "docgen:read"})
|
* @Serializer\Groups({"read", "docgen:read"})
|
||||||
* @Serializer\SerializedName("current_address")
|
* @Serializer\SerializedName("current_address")
|
||||||
@ -157,6 +191,31 @@ class Household
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCurrentComposition(?DateTimeImmutable $at = null): ?HouseholdComposition
|
||||||
|
{
|
||||||
|
$at ??= new DateTimeImmutable('today');
|
||||||
|
$criteria = new Criteria();
|
||||||
|
$expr = Criteria::expr();
|
||||||
|
|
||||||
|
$criteria->where(
|
||||||
|
$expr->andX(
|
||||||
|
$expr->orX(
|
||||||
|
$expr->isNull('endDate'),
|
||||||
|
$expr->gt('endDate', $at)
|
||||||
|
),
|
||||||
|
$expr->lte('startDate', $at)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$compositions = $this->compositions->matching($criteria);
|
||||||
|
|
||||||
|
if ($compositions->count() > 0) {
|
||||||
|
return $compositions->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Serializer\Groups({"docgen:read"})
|
* @Serializer\Groups({"docgen:read"})
|
||||||
*/
|
*/
|
||||||
@ -369,11 +428,54 @@ class Household
|
|||||||
return $this->waitingForBirthDate;
|
return $this->waitingForBirthDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function householdCompositionConsistency(): void
|
||||||
|
{
|
||||||
|
$compositionOrdered = $this->compositions->toArray();
|
||||||
|
|
||||||
|
usort(
|
||||||
|
$compositionOrdered,
|
||||||
|
static function (HouseholdComposition $a, HouseholdComposition $b) {
|
||||||
|
return $a->getStartDate() <=> $b->getStartDate();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$iterator = new ArrayIterator($compositionOrdered);
|
||||||
|
$iterator->rewind();
|
||||||
|
|
||||||
|
/** @var ?HouseholdComposition $previous */
|
||||||
|
$previous = null;
|
||||||
|
|
||||||
|
do {
|
||||||
|
/** @var ?HouseholdComposition $current */
|
||||||
|
$current = $iterator->current();
|
||||||
|
|
||||||
|
if (null !== $previous) {
|
||||||
|
if (null === $previous->getEndDate() || $previous->getEndDate() > $current->getStartDate()) {
|
||||||
|
$previous->setEndDate($current->getStartDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$previous = $current;
|
||||||
|
$iterator->next();
|
||||||
|
} while ($iterator->valid());
|
||||||
|
}
|
||||||
|
|
||||||
public function removeAddress(Address $address)
|
public function removeAddress(Address $address)
|
||||||
{
|
{
|
||||||
$this->addresses->removeElement($address);
|
$this->addresses->removeElement($address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function removeComposition(HouseholdComposition $composition): self
|
||||||
|
{
|
||||||
|
if ($this->compositions->removeElement($composition)) {
|
||||||
|
$composition->setHousehold(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function removeMember(HouseholdMember $member): self
|
public function removeMember(HouseholdMember $member): self
|
||||||
{
|
{
|
||||||
if ($this->members->removeElement($member)) {
|
if ($this->members->removeElement($member)) {
|
||||||
|
@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Entity\Household;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||||
|
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(
|
||||||
|
* name="chill_person_household_composition"
|
||||||
|
* )
|
||||||
|
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||||
|
* "household_composition_type": HouseholdCompositionType::class
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterface
|
||||||
|
{
|
||||||
|
use TrackCreationTrait;
|
||||||
|
use TrackUpdateTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_")
|
||||||
|
*/
|
||||||
|
private CommentEmbeddable $comment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
||||||
|
* @Assert\GreaterThanOrEqual(propertyPath="startDate", groups={"Default", "household_composition"})
|
||||||
|
*/
|
||||||
|
private ?DateTimeImmutable $endDate = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=Household::class, inversedBy="compositions")
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private ?Household $household = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=HouseholdCompositionType::class)
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private ?HouseholdCompositionType $householdCompositionType = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
* @Serializer\Groups({"read", "docgen:read"})
|
||||||
|
*/
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="integer", nullable=true, options={"default": null})
|
||||||
|
* @Assert\NotNull
|
||||||
|
* @Assert\GreaterThanOrEqual(0, groups={"Default", "household_composition"})
|
||||||
|
*/
|
||||||
|
private ?int $numberOfChildren = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="date_immutable", nullable=false)
|
||||||
|
* @Assert\NotNull(groups={"Default", "household_composition"})
|
||||||
|
*/
|
||||||
|
private ?DateTimeImmutable $startDate = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->comment = new CommentEmbeddable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComment(): CommentEmbeddable
|
||||||
|
{
|
||||||
|
return $this->comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEndDate(): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHousehold(): ?Household
|
||||||
|
{
|
||||||
|
return $this->household;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHouseholdCompositionType(): ?HouseholdCompositionType
|
||||||
|
{
|
||||||
|
return $this->householdCompositionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNumberOfChildren(): ?int
|
||||||
|
{
|
||||||
|
return $this->numberOfChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStartDate(): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setComment(CommentEmbeddable $comment): HouseholdComposition
|
||||||
|
{
|
||||||
|
$this->comment = $comment;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEndDate(?DateTimeImmutable $endDate): HouseholdComposition
|
||||||
|
{
|
||||||
|
$this->endDate = $endDate;
|
||||||
|
|
||||||
|
if (null !== $this->household) {
|
||||||
|
$this->household->householdCompositionConsistency();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHousehold(?Household $household): HouseholdComposition
|
||||||
|
{
|
||||||
|
$this->household = $household;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHouseholdCompositionType(?HouseholdCompositionType $householdCompositionType): HouseholdComposition
|
||||||
|
{
|
||||||
|
$this->householdCompositionType = $householdCompositionType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNumberOfChildren(?int $numberOfChildren): HouseholdComposition
|
||||||
|
{
|
||||||
|
$this->numberOfChildren = $numberOfChildren;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStartDate(?DateTimeImmutable $startDate): HouseholdComposition
|
||||||
|
{
|
||||||
|
$this->startDate = $startDate;
|
||||||
|
|
||||||
|
if (null !== $this->household) {
|
||||||
|
$this->household->householdCompositionConsistency();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Entity\Household;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(
|
||||||
|
* name="chill_person_household_composition_type"
|
||||||
|
* )
|
||||||
|
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||||
|
* "household_composition_type": HouseholdCompositionType::class
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class HouseholdCompositionType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="boolean")
|
||||||
|
*/
|
||||||
|
private bool $active = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
* @Serializer\Groups({"read", "docgen:read"})
|
||||||
|
*/
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="json")
|
||||||
|
* @Serializer\Groups({"read", "docgen:read"})
|
||||||
|
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
|
||||||
|
*/
|
||||||
|
private array $label = [];
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): array
|
||||||
|
{
|
||||||
|
return $this->label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isActive(): bool
|
||||||
|
{
|
||||||
|
return $this->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setActive(bool $active): HouseholdCompositionType
|
||||||
|
{
|
||||||
|
$this->active = $active;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLabel(array $label): HouseholdCompositionType
|
||||||
|
{
|
||||||
|
$this->label = $label;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -1142,7 +1142,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
->where(
|
->where(
|
||||||
$expr->eq('shareHousehold', true)
|
$expr->eq('shareHousehold', true)
|
||||||
)
|
)
|
||||||
->orderBy(['startDate' => Criteria::DESC]);
|
->orderBy(['startDate' => Criteria::DESC, 'id' => Criteria::DESC]);
|
||||||
|
|
||||||
return $this->getHouseholdParticipations()
|
return $this->getHouseholdParticipations()
|
||||||
->matching($criteria);
|
->matching($criteria);
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||||
|
use Chill\MainBundle\Form\Type\CommentType;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Chill\PersonBundle\Repository\Household\HouseholdCompositionTypeRepository;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class HouseholdCompositionType extends AbstractType
|
||||||
|
{
|
||||||
|
private HouseholdCompositionTypeRepository $householdCompositionTypeRepository;
|
||||||
|
|
||||||
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(HouseholdCompositionTypeRepository $householdCompositionTypeRepository, TranslatableStringHelperInterface $translatableStringHelper)
|
||||||
|
{
|
||||||
|
$this->householdCompositionTypeRepository = $householdCompositionTypeRepository;
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$types = $this->householdCompositionTypeRepository->findAllActive();
|
||||||
|
|
||||||
|
$builder
|
||||||
|
->add('householdCompositionType', EntityType::class, [
|
||||||
|
'class' => \Chill\PersonBundle\Entity\Household\HouseholdCompositionType::class,
|
||||||
|
'choices' => $types,
|
||||||
|
'choice_label' => function (\Chill\PersonBundle\Entity\Household\HouseholdCompositionType $type) {
|
||||||
|
return $this->translatableStringHelper->localize($type->getLabel());
|
||||||
|
},
|
||||||
|
'label' => 'household_composition.Household composition',
|
||||||
|
])
|
||||||
|
->add('startDate', ChillDateType::class, [
|
||||||
|
'required' => true,
|
||||||
|
'input' => 'datetime_immutable',
|
||||||
|
])
|
||||||
|
->add('numberOfChildren', IntegerType::class, [
|
||||||
|
'required' => true,
|
||||||
|
'label' => 'household_composition.numberOfChildren',
|
||||||
|
])
|
||||||
|
->add('comment', CommentType::class, [
|
||||||
|
'required' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,8 @@ class MembersEditor
|
|||||||
{
|
{
|
||||||
public const VALIDATION_GROUP_AFFECTED = 'household_memberships';
|
public const VALIDATION_GROUP_AFFECTED = 'household_memberships';
|
||||||
|
|
||||||
|
public const VALIDATION_GROUP_COMPOSITION = 'household_composition';
|
||||||
|
|
||||||
public const VALIDATION_GROUP_CREATED = 'household_memberships_created';
|
public const VALIDATION_GROUP_CREATED = 'household_memberships_created';
|
||||||
|
|
||||||
private ?Household $household = null;
|
private ?Household $household = null;
|
||||||
@ -77,6 +79,15 @@ class MembersEditor
|
|||||||
$this->oldMembershipsHashes[] = spl_object_hash($participation);
|
$this->oldMembershipsHashes[] = spl_object_hash($participation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
|
||||||
|
if ($participation->getHousehold() === $this->household
|
||||||
|
&& $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()
|
||||||
|
&& $participation->getStartDate() <= $membership->getStartDate()
|
||||||
|
) {
|
||||||
|
$participation->setEndDate($membership->getStartDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->membershipsAffected[] = $membership;
|
$this->membershipsAffected[] = $membership;
|
||||||
@ -129,7 +140,7 @@ class MembersEditor
|
|||||||
{
|
{
|
||||||
if ($this->hasHousehold()) {
|
if ($this->hasHousehold()) {
|
||||||
$list = $this->validator
|
$list = $this->validator
|
||||||
->validate($this->getHousehold(), null, [self::VALIDATION_GROUP_AFFECTED]);
|
->validate($this->getHousehold(), null, [self::VALIDATION_GROUP_AFFECTED, self::VALIDATION_GROUP_COMPOSITION]);
|
||||||
} else {
|
} else {
|
||||||
$list = new ConstraintViolationList();
|
$list = new ConstraintViolationList();
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
|
|
||||||
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
|
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
|
||||||
{
|
{
|
||||||
|
/** @var \Chill\PersonBundle\Entity\Household\Household $household */
|
||||||
$household = $parameters['household'];
|
$household = $parameters['household'];
|
||||||
|
|
||||||
$menu->addChild($this->translator->trans('household.Household summary'), [
|
$menu->addChild($this->translator->trans('household.Household summary'), [
|
||||||
@ -38,6 +39,20 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
], ])
|
], ])
|
||||||
->setExtras(['order' => 10]);
|
->setExtras(['order' => 10]);
|
||||||
|
|
||||||
|
$menu->addChild($this->translator->trans('household.Relationship'), [
|
||||||
|
'route' => 'chill_person_household_relationship',
|
||||||
|
'routeParameters' => [
|
||||||
|
'household_id' => $household->getId(),
|
||||||
|
], ])
|
||||||
|
->setExtras(['order' => 15]);
|
||||||
|
|
||||||
|
$menu->addChild($this->translator->trans('household_composition.Compositions'), [
|
||||||
|
'route' => 'chill_person_household_composition_index',
|
||||||
|
'routeParameters' => [
|
||||||
|
'id' => $household->getId(),
|
||||||
|
], ])
|
||||||
|
->setExtras(['order' => 17]);
|
||||||
|
|
||||||
$menu->addChild($this->translator->trans('household.Accompanying period'), [
|
$menu->addChild($this->translator->trans('household.Accompanying period'), [
|
||||||
'route' => 'chill_person_household_accompanying_period',
|
'route' => 'chill_person_household_accompanying_period',
|
||||||
'routeParameters' => [
|
'routeParameters' => [
|
||||||
@ -51,13 +66,6 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
'household_id' => $household->getId(),
|
'household_id' => $household->getId(),
|
||||||
], ])
|
], ])
|
||||||
->setExtras(['order' => 30]);
|
->setExtras(['order' => 30]);
|
||||||
|
|
||||||
$menu->addChild($this->translator->trans('household.Relationship'), [
|
|
||||||
'route' => 'chill_person_household_relationship',
|
|
||||||
'routeParameters' => [
|
|
||||||
'household_id' => $household->getId(),
|
|
||||||
], ])
|
|
||||||
->setExtras(['order' => 15]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getMenuIds(): array
|
public static function getMenuIds(): array
|
||||||
|
@ -39,7 +39,7 @@ final class HouseholdACLAwareRepository implements HouseholdACLAwareRepositoryIn
|
|||||||
{
|
{
|
||||||
$centers = $this->authorizationHelper->getReachableCenters(
|
$centers = $this->authorizationHelper->getReachableCenters(
|
||||||
$this->security->getUser(),
|
$this->security->getUser(),
|
||||||
HouseholdVoter::SHOW
|
HouseholdVoter::SEE
|
||||||
);
|
);
|
||||||
|
|
||||||
if ([] === $centers) {
|
if ([] === $centers) {
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Repository\Household;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
class HouseholdCompositionRepository implements ObjectRepository
|
||||||
|
{
|
||||||
|
private EntityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
$this->repository = $entityManager->getRepository(HouseholdComposition::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countByHousehold(Household $household): int
|
||||||
|
{
|
||||||
|
return $this->repository->count(['household' => $household]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id): ?HouseholdComposition
|
||||||
|
{
|
||||||
|
return $this->repository->find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|HouseholdComposition[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $offset
|
||||||
|
*
|
||||||
|
* @return array|object[]|HouseholdComposition[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|HouseholdComposition[]|object[]
|
||||||
|
*/
|
||||||
|
public function findByHousehold(Household $household, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||||
|
{
|
||||||
|
return $this->findBy(['household' => $household], $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?HouseholdComposition
|
||||||
|
{
|
||||||
|
return $this->repository->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName(): string
|
||||||
|
{
|
||||||
|
return HouseholdComposition::class;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Repository\Household;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdCompositionType;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
class HouseholdCompositionTypeRepository implements ObjectRepository
|
||||||
|
{
|
||||||
|
private EntityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
$this->repository = $entityManager->getRepository(HouseholdCompositionType::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id): ?HouseholdCompositionType
|
||||||
|
{
|
||||||
|
return $this->repository->find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|HouseholdCompositionType[]|object[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|HouseholdCompositionType[]
|
||||||
|
*/
|
||||||
|
public function findAllActive(): array
|
||||||
|
{
|
||||||
|
return $this->findBy(['active' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $limit
|
||||||
|
* @param $offset
|
||||||
|
*
|
||||||
|
* @return array|HouseholdCompositionType[]|object[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?HouseholdCompositionType
|
||||||
|
{
|
||||||
|
return $this->repository->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName(): string
|
||||||
|
{
|
||||||
|
return HouseholdCompositionType::class;
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li v-else>
|
<li v-else>
|
||||||
<button class="btn btn-save" @click="confirm" :disabled="hasWarnings">
|
<button class="btn btn-save" @click="confirm" :disabled="hasWarnings || !lastStepIsSaveAllowed">
|
||||||
{{ $t('household_members_editor.app.save') }}
|
{{ $t('household_members_editor.app.save') }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@ -104,6 +104,13 @@ export default {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
lastStepIsSaveAllowed() {
|
||||||
|
let r = !this.$store.getters.isHouseholdNew ||
|
||||||
|
(this.$store.state.numberOfChildren !== null && this.$store.state.householdCompositionType !== null);
|
||||||
|
console.log('is saved allowed ?', r);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
goToNext() {
|
goToNext() {
|
||||||
|
@ -7,18 +7,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<p>
|
<p>{{ $t('household_members_editor.concerned.persons_will_be_moved') }} :</p>
|
||||||
{{ $t('household_members_editor.concerned.persons_will_be_moved') }} :
|
|
||||||
<span v-for="c in concerned" :key="c.person.id">
|
<ul class="list-suggest remove-items inline">
|
||||||
<person-render-box render="badge" :options="{addLink: false}" :person="c.person"></person-render-box>
|
<li v-for="c in concerned" :key="c.person.id" @click="removeConcerned(c)">
|
||||||
<button class="btn" @click="removePerson(c.person)" v-if="c.allowRemove" style="padding-left:0;">
|
<span>{{ c.person.text }}</span>
|
||||||
<span class="fa-stack fa-lg" :title="$t('household_members_editor.concerned.remove_concerned')">
|
</li>
|
||||||
<i class="fa fa-circle fa-stack-1x text-danger"></i>
|
</ul>
|
||||||
<i class="fa fa-times fa-stack-1x"></i>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<div class="alert alert-info" v-if="concernedPersonsWithHouseholds.length > 0">
|
<div class="alert alert-info" v-if="concernedPersonsWithHouseholds.length > 0">
|
||||||
<p>{{ $t('household_members_editor.concerned.persons_with_household') }}</p>
|
<p>{{ $t('household_members_editor.concerned.persons_with_household') }}</p>
|
||||||
<ul v-for="c in concernedPersonsWithHouseholds" :key="c.person.id">
|
<ul v-for="c in concernedPersonsWithHouseholds" :key="c.person.id">
|
||||||
@ -108,9 +104,14 @@ export default {
|
|||||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||||
modal.showModal = false;
|
modal.showModal = false;
|
||||||
},
|
},
|
||||||
removePerson(person) {
|
removeConcerned(concerned) {
|
||||||
console.log('remove person in concerned', person);
|
console.log('removedconcerned', concerned);
|
||||||
this.$store.dispatch('removePerson', person);
|
|
||||||
|
if (!concerned.allowRemove) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch('removePerson', concerned.person);
|
||||||
},
|
},
|
||||||
makeHouseholdLink(id) {
|
makeHouseholdLink(id) {
|
||||||
return `/fr/person/household/${id}/summary`
|
return `/fr/person/household/${id}/summary`
|
||||||
|
@ -4,17 +4,38 @@
|
|||||||
|
|
||||||
<h2>{{ $t('household_members_editor.dates.dates_title') }}</h2>
|
<h2>{{ $t('household_members_editor.dates.dates_title') }}</h2>
|
||||||
|
|
||||||
<p>
|
<div class="mb-3 row">
|
||||||
<label for="start_date">
|
<label for="start_date" class="col-form-label col-sm-4 required">
|
||||||
{{ $t('household_members_editor.dates.start_date') }}
|
{{ $t('household_members_editor.dates.start_date') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="date" v-model="startDate" />
|
<div class="col-sm-8">
|
||||||
</p>
|
<input type="date" v-model="startDate" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="this.isHouseholdNew">
|
||||||
|
<h2>{{ $t('household_members_editor.composition.composition') }}</h2>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-form-label col-sm-4 required">{{ $t('household_members_editor.composition.household_composition') }}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<select v-model="householdCompositionType" class="form-select form-control">
|
||||||
|
<option v-for="t in householdCompositionTypes" :key="t.id" :value="t.id">{{ t.label.fr }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-form-label col-sm-4 required">{{ $t('household_members_editor.composition.number_of_children') }}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="number" v-model="numberOfChildren" min="0" max="30" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import CurrentHousehold from "./CurrentHousehold";
|
import CurrentHousehold from "./CurrentHousehold";
|
||||||
|
import {mapGetters, mapState} from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Dates',
|
name: 'Dates',
|
||||||
@ -22,6 +43,27 @@ export default {
|
|||||||
CurrentHousehold
|
CurrentHousehold
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(['householdCompositionTypes']),
|
||||||
|
...mapGetters(['isHouseholdNew']),
|
||||||
|
householdCompositionType: {
|
||||||
|
get() {
|
||||||
|
if (this.$store.state.householdCompositionType !== null) {
|
||||||
|
return this.$store.state.householdCompositionType.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit('setHouseholdCompositionType', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
numberOfChildren: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.numberOfChildren;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit('setNumberOfChildren', value);
|
||||||
|
}
|
||||||
|
},
|
||||||
startDate: {
|
startDate: {
|
||||||
get() {
|
get() {
|
||||||
return [
|
return [
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<ckeditor
|
||||||
|
name="content"
|
||||||
|
v-bind:placeholder="$t('comment.content')"
|
||||||
|
:editor="editor"
|
||||||
|
v-model="content"
|
||||||
|
tag-name="textarea">
|
||||||
|
</ckeditor>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||||
|
import ClassicEditor from "ChillMainAssets/module/ckeditor5";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "PersonComment.vue",
|
||||||
|
components: {
|
||||||
|
ckeditor: CKEditor.component,
|
||||||
|
},
|
||||||
|
props: ['conc'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: ClassicEditor,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
content: {
|
||||||
|
get() {
|
||||||
|
return this.$props.conc.comment || '';
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
console.log('set content', value);
|
||||||
|
this.$store.commit('setComment', {conc: this.$props.conc, comment: value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -3,15 +3,15 @@
|
|||||||
|
|
||||||
<h2>{{ $t('household_members_editor.positioning.persons_to_positionnate')}}</h2>
|
<h2>{{ $t('household_members_editor.positioning.persons_to_positionnate')}}</h2>
|
||||||
|
|
||||||
<div class="list-household-members">
|
<div class="list-household-members flex-table">
|
||||||
<div
|
<div
|
||||||
v-for="conc in concerned"
|
v-for="conc in concerned"
|
||||||
class="item-bloc"
|
class="item-bloc"
|
||||||
v-bind:key="conc.person.id"
|
v-bind:key="conc.person.id"
|
||||||
>
|
>
|
||||||
<div class="pick-position">
|
<div class="pick-position item-row">
|
||||||
<div class="person">
|
<div class="person">
|
||||||
<person-render-box render="badge" :options="{}" :person="conc.person"></person-render-box>
|
<h3>{{ conc.person.text }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="holder">
|
<div class="holder">
|
||||||
<button
|
<button
|
||||||
@ -37,6 +37,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="item-row">
|
||||||
|
<div>
|
||||||
|
<h6>{{ $t('household_members_editor.positioning.comment') }}</h6>
|
||||||
|
<person-comment :conc="conc"></person-comment>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -46,12 +52,14 @@ import MemberDetails from './MemberDetails.vue';
|
|||||||
import {mapGetters, mapState} from "vuex";
|
import {mapGetters, mapState} from "vuex";
|
||||||
import CurrentHousehold from "./CurrentHousehold";
|
import CurrentHousehold from "./CurrentHousehold";
|
||||||
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
||||||
|
import PersonComment from './PersonComment';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Positioning",
|
name: "Positioning",
|
||||||
components: {
|
components: {
|
||||||
CurrentHousehold,
|
CurrentHousehold,
|
||||||
PersonRenderBox,
|
PersonRenderBox,
|
||||||
|
PersonComment,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
|
@ -52,6 +52,7 @@ const appMessages = {
|
|||||||
positioning: {
|
positioning: {
|
||||||
persons_to_positionnate: 'Usagers à positionner',
|
persons_to_positionnate: 'Usagers à positionner',
|
||||||
holder: "Titulaire",
|
holder: "Titulaire",
|
||||||
|
comment: "Commentaire",
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
next: 'Suivant',
|
next: 'Suivant',
|
||||||
@ -77,7 +78,12 @@ const appMessages = {
|
|||||||
dates: {
|
dates: {
|
||||||
start_date: "Début de validité",
|
start_date: "Début de validité",
|
||||||
end_date: "Fin de validité",
|
end_date: "Fin de validité",
|
||||||
dates_title: "Période de validité",
|
dates_title: "Depuis le",
|
||||||
|
},
|
||||||
|
composition: {
|
||||||
|
composition: "Composition familiale",
|
||||||
|
household_composition: "Composition du ménage",
|
||||||
|
number_of_children: "Nombre d'enfants mineurs au sein du ménage",
|
||||||
},
|
},
|
||||||
confirmation: {
|
confirmation: {
|
||||||
save: "Enregistrer",
|
save: "Enregistrer",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createStore } from 'vuex';
|
import { createStore } from 'vuex';
|
||||||
import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js';
|
import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js';
|
||||||
|
import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods.js'
|
||||||
import { fetchHouseholdByAddressReference } from 'ChillPersonAssets/lib/household.js';
|
import { fetchHouseholdByAddressReference } from 'ChillPersonAssets/lib/household.js';
|
||||||
import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js';
|
import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js';
|
||||||
|
|
||||||
@ -54,8 +55,11 @@ const store = createStore({
|
|||||||
*/
|
*/
|
||||||
householdSuggestionByAccompanyingPeriod: [], // TODO rename into householdsSuggestion
|
householdSuggestionByAccompanyingPeriod: [], // TODO rename into householdsSuggestion
|
||||||
showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1,
|
showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1,
|
||||||
|
householdCompositionType: null,
|
||||||
|
numberOfChildren: 0,
|
||||||
addressesSuggestion: [],
|
addressesSuggestion: [],
|
||||||
showAddressSuggestion: true,
|
showAddressSuggestion: true,
|
||||||
|
householdCompositionTypes: [],
|
||||||
warnings: [],
|
warnings: [],
|
||||||
errors: []
|
errors: []
|
||||||
},
|
},
|
||||||
@ -250,7 +254,8 @@ const store = createStore({
|
|||||||
payload_conc,
|
payload_conc,
|
||||||
payload = {
|
payload = {
|
||||||
concerned: [],
|
concerned: [],
|
||||||
destination: null
|
destination: null,
|
||||||
|
composition: null,
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
@ -261,7 +266,6 @@ const store = createStore({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (getters.isHouseholdNew && state.household.current_address !== null) {
|
if (getters.isHouseholdNew && state.household.current_address !== null) {
|
||||||
console.log(state.household);
|
|
||||||
payload.destination.forceAddress = { id: state.household.current_address.address_id };
|
payload.destination.forceAddress = { id: state.household.current_address.address_id };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,6 +294,19 @@ const store = createStore({
|
|||||||
payload.concerned.push(payload_conc);
|
payload.concerned.push(payload_conc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getters.isHouseholdNew) {
|
||||||
|
payload.composition = {
|
||||||
|
household_composition_type: {
|
||||||
|
type: state.householdCompositionType.type,
|
||||||
|
id: state.householdCompositionType.id,
|
||||||
|
},
|
||||||
|
number_of_children: state.numberOfChildren,
|
||||||
|
start_date: {
|
||||||
|
datetime: datetimeToISO(state.startDate),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -409,6 +426,15 @@ const store = createStore({
|
|||||||
setErrors(state, errors) {
|
setErrors(state, errors) {
|
||||||
state.errors = errors;
|
state.errors = errors;
|
||||||
},
|
},
|
||||||
|
setHouseholdCompositionTypes(state, types) {
|
||||||
|
state.householdCompositionTypes = types;
|
||||||
|
},
|
||||||
|
setHouseholdCompositionType(state, id) {
|
||||||
|
state.householdCompositionType = state.householdCompositionTypes.find(t => t.id = id);
|
||||||
|
},
|
||||||
|
setNumberOfChildren(state, number) {
|
||||||
|
state.numberOfChildren = Number.parseInt(number);
|
||||||
|
},
|
||||||
addAddressesSuggestion(state, addresses) {
|
addAddressesSuggestion(state, addresses) {
|
||||||
let existingIds = state.addressesSuggestion
|
let existingIds = state.addressesSuggestion
|
||||||
.map(a => a.address_id);
|
.map(a => a.address_id);
|
||||||
@ -570,4 +596,8 @@ if (concerned.length > 0) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchResults(`/api/1.0/person/houehold/composition/type.json`).then(types => {
|
||||||
|
store.commit('setHouseholdCompositionTypes', types);
|
||||||
|
})
|
||||||
|
|
||||||
export { store };
|
export { store };
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
{%- set countPersonLocation = accompanyingCourse.availablePersonLocation|length -%}
|
{%- set countPersonLocation = accompanyingCourse.availablePersonLocation|length -%}
|
||||||
{%- set hasPersonLocation = countPersonLocation > 0 -%}
|
{%- set hasPersonLocation = countPersonLocation > 0 -%}
|
||||||
<div class="alert alert-danger {% if hasPersonLocation %}alert-with-actions{% endif %}">
|
<div class="alert alert-danger {% if hasPersonLocation %}alert-with-actions{% endif %}">
|
||||||
<div class="float-button bottom"><div class="box">
|
<div class="float-button bottom">
|
||||||
<div class="action">
|
<div class="box">
|
||||||
<ul class="record_actions">
|
<div class="action">
|
||||||
<li>
|
<ul class="record_actions">
|
||||||
<a class="btn btn-sm btn-update change-icon"
|
<li>
|
||||||
href="{{ path('chill_person_accompanying_course_edit', { 'accompanying_period_id': accompanyingCourse.id, '_fragment': 'section-20' }) }}">
|
<a class="btn btn-sm btn-update change-icon"
|
||||||
<i class="fa fa-fw fa-crosshairs"></i>
|
href="{{ path('chill_person_accompanying_course_edit', { 'accompanying_period_id': accompanyingCourse.id, '_fragment': 'section-20' }) }}">
|
||||||
{{ 'fix it'|trans }}
|
<i class="fa fa-fw fa-crosshairs"></i>
|
||||||
</a>
|
{{ 'fix it'|trans }}
|
||||||
</li>
|
</a>
|
||||||
</ul>
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}</p>
|
||||||
|
{% if not hasPersonLocation %}
|
||||||
|
<p>
|
||||||
|
{{ 'Associate at least one member with an household, and set an address to this household'|trans }}</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<p>
|
</div>
|
||||||
{{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}</p>
|
|
||||||
{% if not hasPersonLocation %}
|
|
||||||
<p>
|
|
||||||
{{ 'Associate at least one member with an household, and set an address to this household'|trans }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
{% block title 'household.Edit household members'|trans %}
|
{% block title 'household.Edit household members'|trans %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-md-10 col-xxl household-members">
|
<div class="row">
|
||||||
|
<div class="col-md-10 col-xxl household-members">
|
||||||
|
|
||||||
<h1>{{ block('title') }}</h1>
|
<h1>{{ block('title') }}</h1>
|
||||||
<div id="household_members_editor"></div>
|
<div id="household_members_editor"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
@ -51,7 +51,42 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="item-bloc col-7 col-comment">
|
<div class="item-bloc col-7 col-comment">
|
||||||
{% if form is null %}
|
{% if form is null %}
|
||||||
|
{% set currentComposition = household.currentComposition %}
|
||||||
|
{% if currentComposition is not null %}
|
||||||
|
<div>
|
||||||
|
<h6>
|
||||||
|
{{ currentComposition.householdCompositionType.label|localize_translatable_string }}
|
||||||
|
</h6>
|
||||||
|
<p>
|
||||||
|
{{ 'household_composition.numberOfChildren children in household'|trans({'numberOfChildren': currentComposition.numberOfChildren}) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ 'household_composition.Since'|trans({'startDate': currentComposition.startDate}) }}
|
||||||
|
</p>
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-sm btn-update change-icon"
|
||||||
|
href="{{ path('chill_person_household_composition_index', {'id': household.id}) }}">
|
||||||
|
{{ 'household_composition.Update composition'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<p>
|
||||||
|
{{ 'household_composition.Currently no composition'|trans }}
|
||||||
|
</p>
|
||||||
|
<ul class="record_actions" style="margin-bottom: 0">
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-sm btn-update change-icon"
|
||||||
|
href="{{ path('chill_person_household_composition_index', {'id': household.id}) }}">
|
||||||
|
{{ 'household_composition.Add a composition'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if household.waitingForBirth or not household.commentMembers.isEmpty() %}
|
{% if household.waitingForBirth or not household.commentMembers.isEmpty() %}
|
||||||
<div class="p-4 bg-light">
|
<div class="p-4 bg-light">
|
||||||
{% if household.waitingForBirth %}
|
{% if household.waitingForBirth %}
|
||||||
@ -176,13 +211,26 @@
|
|||||||
<span class="unfolded text-secondary">{{ 'household.Hide memberships'|trans }}</span>
|
<span class="unfolded text-secondary">{{ 'household.Hide memberships'|trans }}</span>
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
{% macro buttonsOldMembers(member) %}
|
||||||
|
{% set household = member.person.getCurrentHousehold %}
|
||||||
|
{% if household is not null %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_person_household_summary', { 'household_id': household.id }) }}" class="btn btn-sm btn-chill-beige"><i class="fa fa-home"></i></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
<div id="collapse_{{ p == '_none' ? '_none' : p.id }}"
|
<div id="collapse_{{ p == '_none' ? '_none' : p.id }}"
|
||||||
class="accordion-collapse collapse"
|
class="accordion-collapse collapse"
|
||||||
aria-labelledby="heading_{{ p == '_none' ? '_none' : p.id }}"
|
aria-labelledby="heading_{{ p == '_none' ? '_none' : p.id }}"
|
||||||
data-bs-parent="#nonCurrent">
|
data-bs-parent="#nonCurrent">
|
||||||
<div class="flex-table my-0 list-household-members">
|
<div class="flex-table my-0 list-household-members">
|
||||||
{% for m in old_members %}
|
{% for m in old_members %}
|
||||||
{% include '@ChillPerson/Household/_render_member.html.twig' with { 'member': m } %}
|
{% include '@ChillPerson/Household/_render_member.html.twig' with {
|
||||||
|
'member': m,
|
||||||
|
'customButtons': { 'before': _self.buttonsOldMembers(m) }
|
||||||
|
} %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
{% extends '@ChillPerson/Household/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block title 'household_composition.Compositions'|trans %}
|
||||||
|
|
||||||
|
{% block block_post_menu %}
|
||||||
|
<div class="post-menu"></div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="household_composition">
|
||||||
|
<h1>{{ block('title') }}</h1>
|
||||||
|
|
||||||
|
{% if compositions|length == 0 %}
|
||||||
|
<p class="chill-no-data-statement">{{ 'household_composition.No composition yet'|trans }}</p>
|
||||||
|
{% else %}
|
||||||
|
<div class="flex-table">
|
||||||
|
{% for c in compositions %}
|
||||||
|
{% if c.id != editId %}
|
||||||
|
<div class="item-bloc">
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col">
|
||||||
|
<h3>{{ c.householdCompositionType.label|localize_translatable_string }}</h3>
|
||||||
|
<p>{{ 'household_composition.numberOfChildren'|trans }}: {{ c.numberOfChildren }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="item-col">{{ 'household_composition.Since'|trans({'startDate': c.startDate}) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col">
|
||||||
|
{% if c.endDate is null %}
|
||||||
|
<span class="chill-no-data-statement">{{ 'household_composition.Still active'|trans }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ 'household_composition.Until'|trans({'endDate': c.endDate })}}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if c.comment.comment is not empty %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
{{ c.comment|chill_entity_render_box }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_PERSON_HOUSEHOLD_EDIT', c.household) %}
|
||||||
|
<div class="item-row">
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id, 'edit': c.id}) }}" class="btn btn-edit"></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id, 'edit': c.id}) }}" class="btn btn-edit"></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form_start(form) }}
|
||||||
|
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li class="cancel">
|
||||||
|
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id}) }}">{{ 'Cancel'|trans }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="submit" class="btn btn-create">{{ 'Save'|trans }}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{ form_end(form) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="collapseForm" class="{% if not isPosted %}collapse{% endif %}">
|
||||||
|
{{ form_start(form) }}
|
||||||
|
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<button type="submit" class="btn btn-create">{{ 'Save'|trans }}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{ form_end(form) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if editId == -1 %}
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-primary btn-create change-icon" type="button" data-bs-toggle="collapse" data-bs-target="#collapseForm" aria-expanded="false" aria-controls="collapseForm">
|
||||||
|
{{ 'Create'|trans }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -11,7 +11,66 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\Security\Authorization;
|
namespace Chill\PersonBundle\Security\Authorization;
|
||||||
|
|
||||||
class HouseholdVoter
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use UnexpectedValueException;
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
class HouseholdVoter extends Voter
|
||||||
{
|
{
|
||||||
public const SHOW = PersonVoter::SEE;
|
public const EDIT = 'CHILL_PERSON_HOUSEHOLD_EDIT';
|
||||||
|
|
||||||
|
public const SEE = 'CHILL_PERSON_HOUSEHOLD_SEE';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use @see{self::SEE} instead
|
||||||
|
*/
|
||||||
|
public const SHOW = self::SEE;
|
||||||
|
|
||||||
|
private const ALL = [
|
||||||
|
self::EDIT, self::SEE,
|
||||||
|
];
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
public function __construct(Security $security)
|
||||||
|
{
|
||||||
|
$this->security = $security;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function supports($attribute, $subject)
|
||||||
|
{
|
||||||
|
return $subject instanceof Household
|
||||||
|
&& in_array($attribute, self::ALL, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
|
||||||
|
{
|
||||||
|
switch ($attribute) {
|
||||||
|
case self::SEE:
|
||||||
|
return $this->checkAssociatedMembersRole($subject, PersonVoter::SEE);
|
||||||
|
|
||||||
|
case self::EDIT:
|
||||||
|
return $this->checkAssociatedMembersRole($subject, PersonVoter::UPDATE);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new UnexpectedValueException('attribute not supported');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkAssociatedMembersRole(Household $household, string $attribute): bool
|
||||||
|
{
|
||||||
|
foreach ($household->getCurrentMembers()->map(static function (HouseholdMember $member) {
|
||||||
|
return $member->getPerson();
|
||||||
|
}) as $person) {
|
||||||
|
if ($this->security->isGranted($attribute, $person)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ declare(strict_types=1);
|
|||||||
namespace Chill\PersonBundle\Serializer\Normalizer;
|
namespace Chill\PersonBundle\Serializer\Normalizer;
|
||||||
|
|
||||||
use Chill\PersonBundle\Entity\Household\Household;
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdCompositionType;
|
||||||
use Chill\PersonBundle\Entity\Household\Position;
|
use Chill\PersonBundle\Entity\Household\Position;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Household\MembersEditor;
|
use Chill\PersonBundle\Household\MembersEditor;
|
||||||
@ -22,6 +24,7 @@ use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
|||||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||||
|
|
||||||
|
use UnexpectedValueException;
|
||||||
use function array_key_exists;
|
use function array_key_exists;
|
||||||
|
|
||||||
class MembersEditorNormalizer implements DenormalizerAwareInterface, DenormalizerInterface
|
class MembersEditorNormalizer implements DenormalizerAwareInterface, DenormalizerInterface
|
||||||
@ -148,6 +151,23 @@ class MembersEditorNormalizer implements DenormalizerAwareInterface, Denormalize
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (null !== $data['composition']) {
|
||||||
|
$compositionType = $this->denormalizer->denormalize($data['composition']['household_composition_type'], HouseholdCompositionType::class, $format, $context);
|
||||||
|
$numberOfChildren = $data['composition']['number_of_children'];
|
||||||
|
$startDate = $this->denormalizer->denormalize($data['composition']['start_date'], DateTimeImmutable::class, $format, $context);
|
||||||
|
|
||||||
|
if (null === $compositionType) {
|
||||||
|
throw new UnexpectedValueException('composition type cannot be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
$householdComposition = (new HouseholdComposition())
|
||||||
|
->setHouseholdCompositionType($compositionType)
|
||||||
|
->setNumberOfChildren($numberOfChildren)
|
||||||
|
->setStartDate($startDate);
|
||||||
|
|
||||||
|
$household->addComposition($householdComposition);
|
||||||
|
}
|
||||||
|
|
||||||
return $editor;
|
return $editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +169,7 @@ final class HouseholdMemberControllerTest extends WebTestCase
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'destination' => null,
|
'destination' => null,
|
||||||
|
'composition' => null,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -223,6 +224,7 @@ final class HouseholdMemberControllerTest extends WebTestCase
|
|||||||
'type' => 'household',
|
'type' => 'household',
|
||||||
'id' => $householdId,
|
'id' => $householdId,
|
||||||
],
|
],
|
||||||
|
'composition' => null,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -272,6 +274,7 @@ final class HouseholdMemberControllerTest extends WebTestCase
|
|||||||
'destination' => [
|
'destination' => [
|
||||||
'type' => 'household',
|
'type' => 'household',
|
||||||
],
|
],
|
||||||
|
'composition' => null,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Entity\Household;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class HouseholdTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testHouseholdComposition()
|
||||||
|
{
|
||||||
|
$household = new Household();
|
||||||
|
|
||||||
|
$household->addComposition(($first = new HouseholdComposition())
|
||||||
|
->setStartDate(new DateTimeImmutable('2021-12-01')));
|
||||||
|
|
||||||
|
$this->assertNull($first->getEndDate());
|
||||||
|
|
||||||
|
$household->addComposition(($second = new HouseholdComposition())
|
||||||
|
->setStartDate(new DateTimeImmutable('2021-12-31')));
|
||||||
|
|
||||||
|
$this->assertEquals(new DateTimeImmutable('2021-12-31'), $first->getEndDate());
|
||||||
|
$this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate());
|
||||||
|
|
||||||
|
$household->addComposition(($inside = new HouseholdComposition())
|
||||||
|
->setStartDate(new DateTimeImmutable('2021-12-15')));
|
||||||
|
|
||||||
|
$this->assertEquals(new DateTimeImmutable('2021-12-15'), $first->getEndDate());
|
||||||
|
$this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate());
|
||||||
|
$this->assertEquals(new DateTimeImmutable('2021-12-31'), $inside->getEndDate());
|
||||||
|
}
|
||||||
|
}
|
@ -25,3 +25,7 @@ services:
|
|||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
tags:
|
tags:
|
||||||
- { name: security.voter }
|
- { name: security.voter }
|
||||||
|
|
||||||
|
Chill\PersonBundle\Security\Authorization\HouseholdVoter:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Person;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20220121121310 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP SEQUENCE chill_person_household_composition_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP SEQUENCE chill_person_household_composition_type_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP TABLE chill_person_household_composition');
|
||||||
|
$this->addSql('DROP TABLE chill_person_household_composition_type');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Create table for household composition';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE SEQUENCE chill_person_household_composition_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE SEQUENCE chill_person_household_composition_type_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE TABLE chill_person_household_composition (id INT NOT NULL, household_id INT NOT NULL, endDate DATE DEFAULT NULL, numberOfChildren INT DEFAULT NULL, startDate DATE NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_comment TEXT DEFAULT NULL, comment_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_userId INT DEFAULT NULL, householdCompositionType_id INT NOT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_E62BEE83E79FF843 ON chill_person_household_composition (household_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_E62BEE83CDEA4FCF ON chill_person_household_composition (householdCompositionType_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_E62BEE833174800F ON chill_person_household_composition (createdBy_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_E62BEE8365FF1AEC ON chill_person_household_composition (updatedBy_id)');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_person_household_composition.endDate IS \'(DC2Type:date_immutable)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_person_household_composition.startDate IS \'(DC2Type:date_immutable)\'');
|
||||||
|
$this->addSql('CREATE TABLE chill_person_household_composition_type (id INT NOT NULL, active BOOLEAN NOT NULL, label JSON NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('ALTER TABLE chill_person_household_composition ADD CONSTRAINT FK_E62BEE83E79FF843 FOREIGN KEY (household_id) REFERENCES chill_person_household (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_person_household_composition ADD CONSTRAINT FK_E62BEE83CDEA4FCF FOREIGN KEY (householdCompositionType_id) REFERENCES chill_person_household_composition_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_person_household_composition ADD CONSTRAINT FK_E62BEE833174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_person_household_composition ADD CONSTRAINT FK_E62BEE8365FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_person_household_composition.createdAt IS \'(DC2Type:date_immutable)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_person_household_composition.updatedAt IS \'(DC2Type:date_immutable)\'');
|
||||||
|
}
|
||||||
|
}
|
@ -98,3 +98,17 @@ household:
|
|||||||
from: Depuis
|
from: Depuis
|
||||||
to: Jusqu'au
|
to: Jusqu'au
|
||||||
person history: Ménages
|
person history: Ménages
|
||||||
|
|
||||||
|
household_composition:
|
||||||
|
Since: >-
|
||||||
|
Depuis le {startDate, date, long}
|
||||||
|
Still active: Toujours actif
|
||||||
|
Until: >-
|
||||||
|
Jusqu'au {endDate, date, long}
|
||||||
|
numberOfChildren children in household: >-
|
||||||
|
{numberOfChildren, plural,
|
||||||
|
=0 {Aucun enfant dans le ménage}
|
||||||
|
one {1 enfant dans le ménage}
|
||||||
|
few {# enfants dans le ménage}
|
||||||
|
other {# enfants dans le ménage}
|
||||||
|
}
|
||||||
|
@ -498,3 +498,14 @@ period_notification:
|
|||||||
You are getting a notification for a period which does not exists any more: Cette notification ne correspond pas à une période d'accompagnement valide.
|
You are getting a notification for a period which does not exists any more: Cette notification ne correspond pas à une période d'accompagnement valide.
|
||||||
You are getting a notification for a period you are not allowed to see: La notification fait référence à une période d'accompagnement à laquelle vous n'avez pas accès.
|
You are getting a notification for a period you are not allowed to see: La notification fait référence à une période d'accompagnement à laquelle vous n'avez pas accès.
|
||||||
This is the minimal period details: Période d'accompagnement n°
|
This is the minimal period details: Période d'accompagnement n°
|
||||||
|
|
||||||
|
household_composition:
|
||||||
|
No composition yet: Aucune composition familiale renseignée
|
||||||
|
Compositions: Composition familiale
|
||||||
|
endDate: Date de fin
|
||||||
|
numberOfChildren: Nombre d'enfants mineurs au sein du ménage
|
||||||
|
Household composition: Composition du ménage
|
||||||
|
Composition added: Information sur la composition familiale ajoutée
|
||||||
|
Currently no composition: Aucune composition famiale renseignée.
|
||||||
|
Add a composition: Ajouter une composition familiale
|
||||||
|
Update composition: Modifier la composition familiale
|
||||||
|
Loading…
x
Reference in New Issue
Block a user