diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e42392a9..8e610ebd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to * [3party]: show parent in list * [3party]: change color for badge "child" * [3party]: fix address creation +* [household members editor] finalisation of editor @@ -40,10 +41,14 @@ and this project adheres to * [FilterOrder]: add development kit for generating filter and ordering in list * [Capitalization of names] person names are capitalized on creation, on prePersist event +* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ; +* [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ; ### test release 2021-10-04 * [Household editor][UI] Update how household suggestion and addresses are picked; + + See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/80 * [AddAddress] Handle address suggestion; * [CenterType][Create a person] when overriding the ACL rules, allow to show a PickCenterType when no centers are reachable by the default ACL. @@ -62,8 +67,30 @@ and this project adheres to https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37 https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221 -* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ; -* [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ; +* [Household editor] suggest only temporarily addresses; + See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/82 +* On-The-Fly modale works for showing, editing and creating person and thirdparty ; +* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ; +* [AddAddress] Handle address suggestion; +* [AddAddress][Entity address]: add a link between address and address reference; +* [Household editor] suggest household by comparing the temporary addresses from courses; -## Test release yyyy-mm-dd + See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/81 +* On-The-Fly modale works for showing, editing and creating person and thirdparty + +## Test released + + + +## Stable releases + +No stable releases for v2+ + +>>>>>>> 107b8131 (update changelog) diff --git a/src/Bundle/ChillMainBundle/Entity/Address.php b/src/Bundle/ChillMainBundle/Entity/Address.php index 4cc5c37ba..06839ec99 100644 --- a/src/Bundle/ChillMainBundle/Entity/Address.php +++ b/src/Bundle/ChillMainBundle/Entity/Address.php @@ -23,7 +23,7 @@ class Address * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") - * @groups({"write"}) + * @Groups({"write"}) */ private $id; @@ -31,7 +31,7 @@ class Address * @var string * * @ORM\Column(type="string", length=255) - * @groups({"write"}) + * @Groups({"write"}) */ private $street = ''; @@ -39,7 +39,7 @@ class Address * @var string * * @ORM\Column(type="string", length=255) - * @groups({"write"}) + * @Groups({"write"}) */ private $streetNumber = ''; @@ -47,7 +47,7 @@ class Address * @var PostalCode * * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") - * @groups({"write"}) + * @Groups({"write"}) */ private $postcode; @@ -55,7 +55,7 @@ class Address * @var string|null * * @ORM\Column(type="string", length=16, nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private $floor; @@ -63,7 +63,7 @@ class Address * @var string|null * * @ORM\Column(type="string", length=16, nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private $corridor; @@ -71,7 +71,7 @@ class Address * @var string|null * * @ORM\Column(type="string", length=16, nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private $steps; @@ -79,7 +79,7 @@ class Address * @var string|null * * @ORM\Column(type="string", length=255, nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private $buildingName; @@ -87,7 +87,7 @@ class Address * @var string|null * * @ORM\Column(type="string", length=16, nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private $flat; @@ -95,7 +95,7 @@ class Address * @var string|null * * @ORM\Column(type="string", length=255, nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private $distribution; @@ -103,7 +103,7 @@ class Address * @var string|null * * @ORM\Column(type="string", length=255, nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private $extra; @@ -114,7 +114,7 @@ class Address * @var \DateTime * * @ORM\Column(type="date") - * @groups({"write"}) + * @Groups({"write"}) */ private \DateTime $validFrom; @@ -125,13 +125,13 @@ class Address * @var \DateTime|null * * @ORM\Column(type="date", nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private ?\DateTime $validTo = null; /** * True if the address is a "no address", aka homeless person, ... - * @groups({"write"}) + * @Groups({"write"}) * @ORM\Column(type="boolean") * * @var bool @@ -144,7 +144,7 @@ class Address * @var Point|null * * @ORM\Column(type="point", nullable=true) - * @groups({"write"}) + * @Groups({"write"}) */ private $point; @@ -154,7 +154,7 @@ class Address * @var ThirdParty|null * * @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty") - * @groups({"write"}) + * @Groups({"write"}) * @ORM\JoinColumn(nullable=true, onDelete="SET NULL") */ private $linkedToThirdParty; @@ -166,6 +166,12 @@ class Address */ private $customs = []; + /** + * @ORM\ManyToOne(targetEntity=AddressReference::class) + * @Groups({"write"}) + */ + private ?AddressReference $addressReference = null; + public function __construct() { $this->validFrom = new \DateTime(); @@ -376,6 +382,7 @@ class Address public static function createFromAddress(Address $original) : Address { return (new Address()) + ->setAddressReference($original->getAddressReference()) ->setBuildingName($original->getBuildingName()) ->setCorridor($original->getCorridor()) ->setCustoms($original->getCustoms()) @@ -402,6 +409,7 @@ class Address ->setPostcode($original->getPostcode()) ->setStreet($original->getStreet()) ->setStreetNumber($original->getStreetNumber()) + ->setAddressReference($original) ; } @@ -549,5 +557,22 @@ class Address return $this; } + /** + * @return AddressReference|null + */ + public function getAddressReference(): ?AddressReference + { + return $this->addressReference; + } + + /** + * @param AddressReference|null $addressReference + * @return Address + */ + public function setAddressReference(?AddressReference $addressReference = null): Address + { + $this->addressReference = $addressReference; + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/download.js b/src/Bundle/ChillMainBundle/Resources/public/lib/api/download.js new file mode 100644 index 000000000..625103ebe --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/download.js @@ -0,0 +1,39 @@ + +const _fetchAction = (page, uri, params) => { + const item_per_page = 50; + if (params === undefined) { + params = {}; + } + let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params }); + + return fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + }).then(response => { + if (response.ok) { return response.json(); } + throw Error({ m: response.statusText }); + }); +}; + +const fetchResults = async (uri, params) => { + let promises = [], + page = 1; + let firstData = await _fetchAction(page, uri, params); + + promises.push(Promise.resolve(firstData.results)); + + if (firstData.pagination.more) { + do { + page = ++page; + promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results))); + } while (page * firstData.pagination.items_per_page < firstData.count) + } + + return Promise.all(promises).then(values => values.flat()); +}; + +export { + fetchResults +}; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js b/src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js index 9073822bb..4c4223d77 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js @@ -1,15 +1,7 @@ +import { fetchResults } from 'ChillMainAssets/lib/api/download.js'; + const fetchScopes = () => { - return window.fetch('/api/1.0/main/scope.json').then(response => { - if (response.ok) { - return response.json(); - } - }).then(data => { - //console.log(data); - return new Promise((resolve, reject) => { - //console.log(data); - resolve(data.results); - }); - }); + return fetchResults('/api/1.0/main/scope.json'); }; export { diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue index 5616ce0be..7160d7cec 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue @@ -589,6 +589,14 @@ export default { 'point': this.entity.selected.address.point.coordinates }); } + + // add the address reference, if any + if (this.entity.selected.address.addressReference !== undefined) { + newAddress = Object.assign(newAddress, { + 'addressReference': this.entity.selected.address.addressReference + }); + } + if (this.validFrom) { console.log('add validFrom in fetch body', this.entity.selected.valid.from); newAddress = Object.assign(newAddress, { @@ -733,6 +741,9 @@ export default { }, /** + * + * Called when the event pick-address is emitted, which is, by the way, + * called when an address suggestion is picked. * * @param address the address selected */ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue index 50a1bd8c1..ca2f5d634 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue @@ -95,6 +95,9 @@ export default { }, selectAddress(value) { this.entity.selected.address = value; + this.entity.selected.address.addressReference = { + id: value.id + }; this.entity.selected.address.street = value.street; this.entity.selected.address.streetNumber = value.streetNumber; this.entity.selected.writeNew.address = false; diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php index 2b39c07c3..ee628e014 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php @@ -3,6 +3,7 @@ namespace Chill\MainBundle\Serializer\Normalizer; use Chill\MainBundle\Entity\Address; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -33,6 +34,9 @@ class AddressNormalizer implements NormalizerAwareInterface, NormalizerInterface $data['extra'] = $address->getExtra(); $data['validFrom'] = $address->getValidFrom(); $data['validTo'] = $address->getValidTo(); + $data['addressReference'] = $this->normalizer->normalize($address->getAddressReference(), $format, [ + AbstractNormalizer::GROUPS => ['read'] + ]); return $data; } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210929192242.php b/src/Bundle/ChillMainBundle/migrations/Version20210929192242.php new file mode 100644 index 000000000..a2fbefa69 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20210929192242.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE chill_main_address ADD addressReference_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT FK_165051F647069464 FOREIGN KEY (addressReference_id) REFERENCES chill_main_address_reference (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_165051F647069464 ON chill_main_address (addressReference_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_address DROP addressReference_id'); + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php index 0bb804689..11e41ba29 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php @@ -4,24 +4,31 @@ namespace Chill\PersonBundle\Controller; use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Entity\AddressReference; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface; use Chill\PersonBundle\Repository\Household\HouseholdRepository; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; class HouseholdApiController extends ApiController { private HouseholdRepository $householdRepository; - public function __construct(HouseholdRepository $householdRepository) - { + private HouseholdACLAwareRepositoryInterface $householdACLAwareRepository; + + public function __construct( + HouseholdRepository $householdRepository, + HouseholdACLAwareRepositoryInterface $householdACLAwareRepository + ) { $this->householdRepository = $householdRepository; + $this->householdACLAwareRepository = $householdACLAwareRepository; } - public function householdAddressApi($id, Request $request, string $_format): Response { @@ -37,7 +44,7 @@ class HouseholdApiController extends ApiController { // TODO add acl - $count = $this->householdRepository->countByAccompanyingPeriodParticipation($person); + $count = $this->householdRepository->countByAccompanyingPeriodParticipation($person); $paginator = $this->getPaginatorFactory()->create($count); if ($count === 0) { @@ -93,4 +100,27 @@ class HouseholdApiController extends ApiController return $this->json(\array_values($addresses), Response::HTTP_OK, [], [ 'groups' => [ 'read' ] ]); } + + /** + * + * @Route("/api/1.0/person/household/by-address-reference/{id}.json", + * name="chill_api_person_household_by_address_reference") + * @param AddressReference $addressReference + * @return \Symfony\Component\HttpFoundation\JsonResponse + */ + public function getHouseholdByAddressReference(AddressReference $addressReference): Response + { + // TODO ACL + $this->denyAccessUnlessGranted('ROLE_USER'); + + $total = $this->householdACLAwareRepository->countByAddressReference($addressReference); + $paginator = $this->getPaginatorFactory()->create($total); + $households = $this->householdACLAwareRepository->findByAddressReference($addressReference, + $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage()); + $collection = new Collection($households, $paginator); + + return $this->json($collection, Response::HTTP_OK, [], [ + AbstractNormalizer::GROUPS => ['read'] + ]); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php b/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php index 17635a8fa..ab39c13bb 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php @@ -77,13 +77,6 @@ class PersonApiController extends ApiController $a = $participation->getAccompanyingPeriod()->getAddressLocation(); $addresses[$a->getId()] = $a; } - if (null !== $personLocation = $participation - ->getAccompanyingPeriod()->getPersonLocation()) { - $a = $personLocation->getCurrentHouseholdAddress(); - if (null !== $a) { - $addresses[$a->getId()] = $a; - } - } } // remove the actual address diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepository.php new file mode 100644 index 000000000..38236df14 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepository.php @@ -0,0 +1,103 @@ +em = $em; + $this->authorizationHelper = $authorizationHelper; + $this->security = $security; + } + + public function countByAddressReference(AddressReference $addressReference): int + { + $qb = $this->buildQueryByAddressReference($addressReference); + $qb = $this->addACL($qb); + + return $qb->select('COUNT(h)') + ->getQuery() + ->getSingleScalarResult(); + } + + public function findByAddressReference( + AddressReference $addressReference, + ?int $firstResult = 0, + ?int $maxResult = 50 + ): array { + $qb = $this->buildQueryByAddressReference($addressReference); + $qb = $this->addACL($qb); + + return $qb + ->select('h') + ->setFirstResult($firstResult) + ->setMaxResults($maxResult) + ->getQuery() + ->getResult(); + } + + public function buildQueryByAddressReference(AddressReference $addressReference): QueryBuilder + { + $qb = $this->em->createQueryBuilder(); + $qb + ->select('h') + ->from(Household::class, 'h') + ->join('h.addresses', 'address') + ->where( + $qb->expr()->eq('address.addressReference', ':reference') + ) + ->setParameter(':reference', $addressReference) + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte('address.validFrom', ':today'), + $qb->expr()->orX( + $qb->expr()->isNull('address.validTo'), + $qb->expr()->gt('address.validTo', ':today') + ) + ) + ) + ->setParameter('today', new \DateTime('today')) + ; + + return $qb; + } + + public function addACL(QueryBuilder $qb, string $alias = 'h'): QueryBuilder + { + $centers = $this->authorizationHelper->getReachableCenters( + $this->security->getUser(), + HouseholdVoter::SHOW + ); + + if ([] === $centers) { + return $qb + ->andWhere("'FALSE' = 'TRUE'"); + } + + $qb + ->join($alias.'.members', 'members') + ->join('members.person', 'person') + ->andWhere( + $qb->expr()->in('person.center', ':centers') + ) + ->setParameter('centers', $centers); + + return $qb; + } +} diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepositoryInterface.php new file mode 100644 index 000000000..56a927ae5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepositoryInterface.php @@ -0,0 +1,23 @@ + { + const url = `/api/1.0/person/household/by-address-reference/${reference.id}.json` + return fetchResults(url); +}; + +export { + fetchHouseholdByAddressReference +}; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue index ff1cab206..52eac5ea6 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue @@ -1,34 +1,150 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue new file mode 100644 index 000000000..1f76c8a5b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue index be332523d..c40cc9cbc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue @@ -1,5 +1,8 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig index c00e8aed1..e99c1dc1a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig @@ -94,7 +94,7 @@