Merge branch 'issue23_addresses' into 'master'

Issue23 addresses

See merge request Chill-Projet/chill-bundles!12
This commit is contained in:
Julien Fastré 2021-05-07 09:27:56 +00:00
commit 3f562449e1
27 changed files with 1647 additions and 125 deletions

View File

@ -0,0 +1,63 @@
<?php
namespace Chill\MainBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\Routing\Annotation\Route;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\AddressReference;
/**
* Class AddressController
*
* @package Chill\MainBundle\Controller
*/
class AddressController extends AbstractController
{
/**
* Get API Data for showing endpoint
*
* @Route(
* "/{_locale}/main/api/1.0/address/{address_id}/show.{_format}",
* name="chill_main_address_api_show"
* )
* @ParamConverter("address", options={"id": "address_id"})
*/
public function showAddress(Address $address, $_format): Response
{
// TODO check ACL ?
switch ($_format) {
case 'json':
return $this->json($address);
default:
throw new BadRequestException('Unsupported format');
}
}
/**
* Get API Data for showing endpoint
*
* @Route(
* "/{_locale}/main/api/1.0/address-reference/{address_reference_id}/show.{_format}",
* name="chill_main_address_reference_api_show"
* )
* @ParamConverter("addressReference", options={"id": "address_reference_id"})
*/
public function showAddressReference(AddressReference $addressReference, $_format): Response
{
// TODO check ACL ?
switch ($_format) {
case 'json':
return $this->json($addressReference);
default:
throw new BadRequestException('Unsupported format');
}
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace Chill\MainBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Persistence\ObjectManager;
use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Doctrine\Model\Point;
/**
* Load reference addresses into database
*
* @author Champs Libres
*/
class LoadAddressReferences extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface {
protected $faker;
public function __construct()
{
$this->faker = \Faker\Factory::create('fr_FR');
}
/**
*
* @var ContainerInterface
*/
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function getOrder() {
return 51;
}
/**
* Create a random point
*
* @return Point
*/
private function getRandomPoint()
{
$lonBrussels = 4.35243;
$latBrussels = 50.84676;
$lon = $lonBrussels + 0.01 * rand(-5, 5);
$lat = $latBrussels + 0.01 * rand(-5, 5);
return Point::fromLonLat($lon, $lat);
}
/**
* Create a random reference address
*
* @return AddressReference
*/
private function getRandomAddressReference()
{
$ar= new AddressReference();
$ar->setRefId($this->faker->numerify('ref-id-######'));
$ar->setStreet($this->faker->streetName);
$ar->setStreetNumber(rand(0,199));
$ar ->setPoint($this->getRandomPoint());
$ar->setPostcode($this->getReference(
LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)]
));
$ar->setMunicipalityCode($ar->getPostcode()->getCode());
return $ar
;
}
public function load(ObjectManager $manager) {
echo "loading some reference address... \n";
for ($i=0; $i<10; $i++) {
$ar = $this->getRandomAddressReference();
$manager->persist($ar);
}
$manager->flush();
}
}

View File

@ -188,7 +188,12 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
$container->prependExtensionConfig('doctrine', array( $container->prependExtensionConfig('doctrine', array(
'dbal' => [ 'dbal' => [
'types' => [ 'types' => [
'dateinterval' => \Chill\MainBundle\Doctrine\Type\NativeDateIntervalType::class 'dateinterval' => [
'class' => \Chill\MainBundle\Doctrine\Type\NativeDateIntervalType::class
],
'point' => [
'class' => \Chill\MainBundle\Doctrine\Type\PointType::class
]
] ]
] ]
)); ));

View File

@ -0,0 +1,103 @@
<?php
namespace Chill\MainBundle\Doctrine\Model;
use \JsonSerializable;
/**
* Description of Point
*
*/
class Point implements JsonSerializable {
private float $lat;
private float $lon;
public static string $SRID = '4326';
private function __construct(float $lon, float $lat)
{
$this->lat = $lat;
$this->lon = $lon;
}
public function toGeoJson(): string
{
$array = $this->toArrayGeoJson();
return \json_encode($array);
}
public function jsonSerialize(): array
{
return $this->toArrayGeoJson();
}
public function toArrayGeoJson(): array
{
return [
"type" => "Point",
"coordinates" => [ $this->lon, $this->lat ]
];
}
/**
*
* @return string
*/
public function toWKT(): string
{
return 'SRID='.self::$SRID.';POINT('.$this->lon.' '.$this->lat.')';
}
/**
*
* @param type $geojson
* @return Point
*/
public static function fromGeoJson(string $geojson): Point
{
$a = json_decode($geojson);
//check if the geojson string is correct
if (NULL === $a or !isset($a->type) or !isset($a->coordinates)){
throw PointException::badJsonString($geojson);
}
if ($a->type != 'Point'){
throw PointException::badGeoType();
}
$lat = $a->coordinates[1];
$lon = $a->coordinates[0];
return Point::fromLonLat($lon, $lat);
}
public static function fromLonLat(float $lon, float $lat): Point
{
if (($lon > -180 && $lon < 180) && ($lat > -90 && $lat < 90))
{
return new Point($lon, $lat);
} else {
throw PointException::badCoordinates($lon, $lat);
}
}
public static function fromArrayGeoJson(array $array): Point
{
if ($array['type'] == 'Point' &&
isset($array['coordinates']))
{
return self::fromLonLat($array['coordinates'][0], $array['coordinates'][1]);
}
}
public function getLat(): float
{
return $this->lat;
}
public function getLon(): float
{
return $this->lon;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Chill\MainBundle\Doctrine\Model;
use \Exception;
/**
* Description of PointException
*
*/
class PointException extends Exception {
public static function badCoordinates($lon, $lat): self
{
return new self("Input coordinates are not valid in the used coordinate system (longitude = $lon , latitude = $lat)");
}
public static function badJsonString($str): self
{
return new self("The JSON string is not valid: $str");
}
public static function badGeoType(): self
{
return new self("The geoJSON object type is not valid");
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Chill\MainBundle\Doctrine\Type;
use Chill\MainBundle\Doctrine\Model\Point;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Chill\MainBundle\Doctrine\Model\PointException;
/**
* A Type for Doctrine to implement the Geography Point type
* implemented by Postgis on postgis+postgresql databases
*
*/
class PointType extends Type {
const POINT = 'point';
/**
*
* @param array $fieldDeclaration
* @param AbstractPlatform $platform
* @return type
*/
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'geometry(POINT,'.Point::$SRID.')';
}
/**
*
* @param type $value
* @param AbstractPlatform $platform
* @return Point
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === NULL){
return NULL;
} else {
return Point::fromGeoJson($value);
}
}
public function getName()
{
return self::POINT;
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value === NULL){
return NULL;
} else {
return $value->toWKT();
}
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToPHPValueSQL($sqlExpr, $platform)
{
return 'ST_AsGeoJSON('.$sqlExpr.') ';
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return $sqlExpr;
}
}

View File

@ -4,6 +4,8 @@ namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Doctrine\Model\Point;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
/** /**
* Address * Address
@ -28,14 +30,14 @@ class Address
* *
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
*/ */
private $streetAddress1 = ''; private $street = '';
/** /**
* @var string * @var string
* *
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
*/ */
private $streetAddress2 = ''; private $streetNumber = '';
/** /**
* @var PostalCode * @var PostalCode
@ -43,7 +45,56 @@ class Address
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
*/ */
private $postcode; private $postcode;
/**
* @var string|null
*
* @ORM\Column(type="string", length=16, nullable=true)
*/
private $floor;
/**
* @var string|null
*
* @ORM\Column(type="string", length=16, nullable=true)
*/
private $corridor;
/**
* @var string|null
*
* @ORM\Column(type="string", length=16, nullable=true)
*/
private $steps;
/**
* @var string|null
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $buildingName;
/**
* @var string|null
*
* @ORM\Column(type="string", length=16, nullable=true)
*/
private $flat;
/**
* @var string|null
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $distribution;
/**
* @var string|null
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $extra;
/** /**
* Indicates when the address starts validation. Used to build an history * Indicates when the address starts validation. Used to build an history
* of address. By default, the current date. * of address. By default, the current date.
@ -53,27 +104,55 @@ class Address
* @ORM\Column(type="date") * @ORM\Column(type="date")
*/ */
private $validFrom; private $validFrom;
/**
* Indicates when the address ends. Used to build an history
* of address.
*
* @var \DateTime|null
*
* @ORM\Column(type="date", nullable=true)
*/
private $validTo;
/** /**
* True if the address is a "no address", aka homeless person, ... * True if the address is a "no address", aka homeless person, ...
* *
* @var bool * @var bool
*/ */
private $isNoAddress = false; private $isNoAddress = false;
/**
* A geospatial field storing the coordinates of the Address
*
* @var Point|null
*
* @ORM\Column(type="point", nullable=true)
*/
private $point;
/**
* A ThirdParty reference for person's addresses that are linked to a third party
*
* @var ThirdParty|null
*
* @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
* @ORM\JoinColumn(nullable=true)
*/
private $linkedToThirdParty;
/** /**
* A list of metadata, added by customizable fields * A list of metadata, added by customizable fields
* *
* @var array * @var array
*/ */
private $customs = []; private $customs = [];
public function __construct() public function __construct()
{ {
$this->validFrom = new \DateTime(); $this->validFrom = new \DateTime();
} }
/** /**
* Get id * Get id
* *
@ -85,7 +164,7 @@ class Address
} }
/** /**
* Set streetAddress1 * Set streetAddress1 (legacy function)
* *
* @param string $streetAddress1 * @param string $streetAddress1
* *
@ -93,23 +172,23 @@ class Address
*/ */
public function setStreetAddress1($streetAddress1) public function setStreetAddress1($streetAddress1)
{ {
$this->streetAddress1 = $streetAddress1 === NULL ? '' : $streetAddress1; $this->street = $streetAddress1 === NULL ? '' : $streetAddress1;
return $this; return $this;
} }
/** /**
* Get streetAddress1 * Get streetAddress1 (legacy function)
* *
* @return string * @return string
*/ */
public function getStreetAddress1() public function getStreetAddress1()
{ {
return $this->streetAddress1; return $this->street;
} }
/** /**
* Set streetAddress2 * Set streetAddress2 (legacy function)
* *
* @param string $streetAddress2 * @param string $streetAddress2
* *
@ -117,19 +196,19 @@ class Address
*/ */
public function setStreetAddress2($streetAddress2) public function setStreetAddress2($streetAddress2)
{ {
$this->streetAddress2 = $streetAddress2 === NULL ? '' : $streetAddress2; $this->streetNumber = $streetAddress2 === NULL ? '' : $streetAddress2;
return $this; return $this;
} }
/** /**
* Get streetAddress2 * Get streetAddress2 (legacy function)
* *
* @return string * @return string
*/ */
public function getStreetAddress2() public function getStreetAddress2()
{ {
return $this->streetAddress2; return $this->streetNumber;
} }
/** /**
@ -155,7 +234,7 @@ class Address
{ {
return $this->postcode; return $this->postcode;
} }
/** /**
* @return \DateTime * @return \DateTime
*/ */
@ -173,19 +252,19 @@ class Address
$this->validFrom = $validFrom; $this->validFrom = $validFrom;
return $this; return $this;
} }
/** /**
* Get IsNoAddress * Get IsNoAddress
* *
* Indicate true if the address is a fake address (homeless, ...) * Indicate true if the address is a fake address (homeless, ...)
* *
* @return bool * @return bool
*/ */
public function getIsNoAddress(): bool public function getIsNoAddress(): bool
{ {
return $this->isNoAddress; return $this->isNoAddress;
} }
/** /**
* @return bool * @return bool
*/ */
@ -196,9 +275,9 @@ class Address
/** /**
* Set IsNoAddress * Set IsNoAddress
* *
* Indicate true if the address is a fake address (homeless, ...) * Indicate true if the address is a fake address (homeless, ...)
* *
* @param bool $isNoAddress * @param bool $isNoAddress
* @return $this * @return $this
*/ */
@ -207,10 +286,10 @@ class Address
$this->isNoAddress = $isNoAddress; $this->isNoAddress = $isNoAddress;
return $this; return $this;
} }
/** /**
* Get customs informations in the address * Get customs informations in the address
* *
* @return array * @return array
*/ */
public function getCustoms(): array public function getCustoms(): array
@ -220,27 +299,27 @@ class Address
/** /**
* Store custom informations in the address * Store custom informations in the address
* *
* @param array $customs * @param array $customs
* @return $this * @return $this
*/ */
public function setCustoms(array $customs): self public function setCustoms(array $customs): self
{ {
$this->customs = $customs; $this->customs = $customs;
return $this; return $this;
} }
/** /**
* Validate the address. * Validate the address.
* *
* Check that: * Check that:
* *
* * if the address is not home address: * * if the address is not home address:
* * the postal code is present * * the postal code is present
* * the valid from is not null * * the valid from is not null
* * the address street 1 is greater than 2 * * the address street 1 is greater than 2
* *
* @param ExecutionContextInterface $context * @param ExecutionContextInterface $context
* @param array $payload * @param array $payload
*/ */
@ -252,18 +331,18 @@ class Address
->atPath('validFrom') ->atPath('validFrom')
->addViolation(); ->addViolation();
} }
if ($this->isNoAddress()) { if ($this->isNoAddress()) {
return; return;
} }
if (empty($this->getStreetAddress1())) { if (empty($this->getStreetAddress1())) {
$context $context
->buildViolation("address.street1-should-be-set") ->buildViolation("address.street1-should-be-set")
->atPath('streetAddress1') ->atPath('streetAddress1')
->addViolation(); ->addViolation();
} }
if (!$this->getPostcode() instanceof PostalCode) { if (!$this->getPostcode() instanceof PostalCode) {
$context $context
->buildViolation("address.postcode-should-be-set") ->buildViolation("address.postcode-should-be-set")
@ -271,7 +350,7 @@ class Address
->addViolation(); ->addViolation();
} }
} }
/** /**
* @param Address $original * @param Address $original
* @return Address * @return Address
@ -286,5 +365,149 @@ class Address
; ;
} }
public function getStreet(): ?string
{
return $this->street;
}
public function setStreet(string $street): self
{
$this->street = $street;
return $this;
}
public function getStreetNumber(): ?string
{
return $this->streetNumber;
}
public function setStreetNumber(string $streetNumber): self
{
$this->streetNumber = $streetNumber;
return $this;
}
public function getFloor(): ?string
{
return $this->floor;
}
public function setFloor(?string $floor): self
{
$this->floor = $floor;
return $this;
}
public function getCorridor(): ?string
{
return $this->corridor;
}
public function setCorridor(?string $corridor): self
{
$this->corridor = $corridor;
return $this;
}
public function getSteps(): ?string
{
return $this->steps;
}
public function setSteps(?string $steps): self
{
$this->steps = $steps;
return $this;
}
public function getBuildingName(): ?string
{
return $this->buildingName;
}
public function setBuildingName(?string $buildingName): self
{
$this->buildingName = $buildingName;
return $this;
}
public function getFlat(): ?string
{
return $this->flat;
}
public function setFlat(?string $flat): self
{
$this->flat = $flat;
return $this;
}
public function getDistribution(): ?string
{
return $this->distribution;
}
public function setDistribution(?string $distribution): self
{
$this->distribution = $distribution;
return $this;
}
public function getExtra(): ?string
{
return $this->extra;
}
public function setExtra(?string $extra): self
{
$this->extra = $extra;
return $this;
}
public function getValidTo(): ?\DateTimeInterface
{
return $this->validTo;
}
public function setValidTo(\DateTimeInterface $validTo): self
{
$this->validTo = $validTo;
return $this;
}
public function getPoint(): ?Point
{
return $this->point;
}
public function setPoint(?Point $point): self
{
$this->point = $point;
return $this;
}
public function getLinkedToThirdParty()
{
return $this->linkedToThirdParty;
}
public function setLinkedToThirdParty($linkedToThirdParty): self
{
$this->linkedToThirdParty = $linkedToThirdParty;
return $this;
}
} }

View File

@ -0,0 +1,165 @@
<?php
namespace Chill\MainBundle\Entity;
use Chill\MainBundle\Entity\AddressReferenceRepository;
use Doctrine\ORM\Mapping as ORM;
use Chill\MainBundle\Doctrine\Model\Point;
/**
* @ORM\Entity(repositoryClass=AddressReferenceRepository::class)
* @ORM\Table(name="chill_main_address_reference")
* @ORM\HasLifecycleCallbacks()
*/
class AddressReference
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $refId;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $street;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $streetNumber;
/**
* @var PostalCode
*
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
*/
private $postcode;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $municipalityCode;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $source;
/**
* A geospatial field storing the coordinates of the Address
*
* @var Point
*
* @ORM\Column(type="point")
*/
private $point;
public function getId(): ?int
{
return $this->id;
}
public function getRefId(): ?string
{
return $this->refId;
}
public function setRefId(string $refId): self
{
$this->refId = $refId;
return $this;
}
public function getStreet(): ?string
{
return $this->street;
}
public function setStreet(?string $street): self
{
$this->street = $street;
return $this;
}
public function getStreetNumber(): ?string
{
return $this->streetNumber;
}
public function setStreetNumber(?string $streetNumber): self
{
$this->streetNumber = $streetNumber;
return $this;
}
/**
* Set postcode
*
* @param PostalCode $postcode
*
* @return Address
*/
public function setPostcode(PostalCode $postcode = null)
{
$this->postcode = $postcode;
return $this;
}
/**
* Get postcode
*
* @return PostalCode
*/
public function getPostcode()
{
return $this->postcode;
}
public function getMunicipalityCode(): ?string
{
return $this->municipalityCode;
}
public function setMunicipalityCode(?string $municipalityCode): self
{
$this->municipalityCode = $municipalityCode;
return $this;
}
public function getSource(): ?string
{
return $this->source;
}
public function setSource(?string $source): self
{
$this->source = $source;
return $this;
}
public function getPoint(): ?Point
{
return $this->point;
}
public function setPoint(?Point $point): self
{
$this->point = $point;
return $this;
}
}

View File

@ -33,8 +33,8 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
* A type to create/update Address entity * A type to create/update Address entity
* *
* Options: * Options:
* *
* - `has_valid_from` (boolean): show if an entry "has valid from" must be * - `has_valid_from` (boolean): show if an entry "has valid from" must be
* shown. * shown.
* - `null_if_empty` (boolean): replace the address type by null if the street * - `null_if_empty` (boolean): replace the address type by null if the street
* or the postCode is empty. This is useful when the address is not required and * or the postCode is empty. This is useful when the address is not required and
@ -45,10 +45,10 @@ class AddressType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder $builder
->add('streetAddress1', TextType::class, array( ->add('street', TextType::class, array(
'required' => !$options['has_no_address'] // true if has no address is false 'required' => !$options['has_no_address'] // true if has no address is false
)) ))
->add('streetAddress2', TextType::class, array( ->add('streetNumber', TextType::class, array(
'required' => false 'required' => false
)) ))
->add('postCode', PostalCodeType::class, array( ->add('postCode', PostalCodeType::class, array(
@ -57,7 +57,7 @@ class AddressType extends AbstractType
'required' => !$options['has_no_address'] // true if has no address is false 'required' => !$options['has_no_address'] // true if has no address is false
)) ))
; ;
if ($options['has_valid_from']) { if ($options['has_valid_from']) {
$builder $builder
->add('validFrom', DateType::class, array( ->add('validFrom', DateType::class, array(
@ -67,7 +67,7 @@ class AddressType extends AbstractType
) )
); );
} }
if ($options['has_no_address']) { if ($options['has_no_address']) {
$builder $builder
->add('isNoAddress', ChoiceType::class, [ ->add('isNoAddress', ChoiceType::class, [
@ -79,12 +79,12 @@ class AddressType extends AbstractType
'label' => 'address.address_homeless' 'label' => 'address.address_homeless'
]); ]);
} }
if ($options['null_if_empty'] === TRUE) { if ($options['null_if_empty'] === TRUE) {
$builder->setDataMapper(new AddressDataMapper()); $builder->setDataMapper(new AddressDataMapper());
} }
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$resolver $resolver

View File

@ -0,0 +1,50 @@
<?php
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\AddressReference;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method AddressReference|null find($id, $lockMode = null, $lockVersion = null)
* @method AddressReference|null findOneBy(array $criteria, array $orderBy = null)
* @method AddressReference[] findAll()
* @method AddressReference[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class AddressReferenceRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, AddressReference::class);
}
// /**
// * @return AddressReference[] Returns an array of AddressReference objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('a')
->andWhere('a.exampleField = :val')
->setParameter('val', $value)
->orderBy('a.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?AddressReference
{
return $this->createQueryBuilder('a')
->andWhere('a.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View File

@ -6,8 +6,8 @@
<div class="chill_address_is_noaddress">{{ 'address.consider homeless'|trans }}</div> <div class="chill_address_is_noaddress">{{ 'address.consider homeless'|trans }}</div>
{% endif %} {% endif %}
<div class="chill_address_address"> <div class="chill_address_address">
{% if address.streetAddress1 is not empty %}<p class="street street1">{{ address.streetAddress1 }}</p>{% endif %} {% if address.street is not empty %}<p class="street street1">{{ address.street }}</p>{% endif %}
{% if address.streetAddress2 is not empty %}<p class="street street2">{{ address.streetAddress2 }}</p>{% endif %} {% if address.streetNumber is not empty %}<p class="street street2">{{ address.streetNumber }}</p>{% endif %}
{% if address.postCode is not empty %} {% if address.postCode is not empty %}
<p class="postalCode"><span class="code">{{ address.postCode.code }}</span> <span class="name">{{ address.postCode.name }}</span></p> <p class="postalCode"><span class="code">{{ address.postCode.code }}</span> <span class="name">{{ address.postCode.name }}</span></p>
<p class="country">{{ address.postCode.country.name|localize_translatable_string }}</p> <p class="country">{{ address.postCode.country.name|localize_translatable_string }}</p>

View File

@ -0,0 +1,119 @@
<?php
namespace Chill\MainBundle\Tests\Doctrine\Model;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Chill\MainBundle\Doctrine\Model\Point;
/**
* Test the point model methods
*
* @author Julien Minet <julien.minet@champs-libres.coop>
*/
class ExportControllerTest extends KernelTestCase
{
public function testToWKT()
{
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);
$this->assertEquals($point->toWKT(),'SRID=4326;POINT(4.8634 50.47382)');
}
public function testToGeojson()
{
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);
$this->assertEquals($point->toGeoJson(),'{"type":"Point","coordinates":[4.8634,50.47382]}');
}
public function testToArrayGeoJson()
{
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);
$this->assertEquals(
$point->toArrayGeoJson(),
[
'type' => 'Point',
'coordinates' => [4.8634, 50.47382]
]
);
}
public function testJsonSerialize()
{
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);
$this->assertEquals(
$point->jsonSerialize(),
[
'type' => 'Point',
'coordinates' => [4.8634, 50.47382]
]
);
}
public function testFromGeoJson()
{
$geojson = '{"type":"Point","coordinates":[4.8634,50.47382]}';
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);;
$this->assertEquals($point, Point::fromGeoJson($geojson));
}
public function testFromLonLat()
{
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);;
$this->assertEquals($point, Point::fromLonLat($lon, $lat));
}
public function testFromArrayGeoJson()
{
$array = [
'type' => 'Point',
'coordinates' => [4.8634, 50.47382]
];
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);;
$this->assertEquals($point, Point::fromArrayGeoJson($array));
}
public function testGetLat()
{
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);;
$this->assertEquals($lat, $point->getLat());
}
public function testGetLon()
{
$lon = 4.8634;
$lat = 50.47382;
$point = $this->preparePoint($lon, $lat);;
$this->assertEquals($lon, $point->getLon());
}
private function preparePoint($lon, $lat)
{
return Point::fromLonLat($lon, $lat);
}
}

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Create the postgis extension
*/
final class Version20210414091001 extends AbstractMigration
{
public function up(Schema $schema) : void
{
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('CREATE EXTENSION IF NOT EXISTS postgis;');
}
public function down(Schema $schema) : void
{
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('DROP EXTENSION IF NOT EXISTS postgis;');
}
public function getDescription(): string
{
return "Enable the postgis extension in public schema";
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add new fields to address, including a Point geometry field.
*/
final class Version20210420115006 extends AbstractMigration
{
public function getDescription() : string
{
return 'Add a Point data type and modify the Address entity';
}
public function up(Schema $schema) : void
{
$this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetaddress1 TO street;');
$this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetaddress2 TO streetNumber;');
$this->addSql('ALTER TABLE chill_main_address ADD floor VARCHAR(16) DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD corridor VARCHAR(16) DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD steps VARCHAR(16) DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD buildingName VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD flat VARCHAR(16) DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD distribution VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD extra VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD validTo DATE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD point geometry(POINT,4326) DEFAULT NULL');
}
public function down(Schema $schema) : void
{
$this->addSql('ALTER TABLE chill_main_address RENAME COLUMN street TO streetaddress1;');
$this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetNumber TO streetaddress2;');
$this->addSql('ALTER TABLE chill_main_address DROP floor');
$this->addSql('ALTER TABLE chill_main_address DROP corridor');
$this->addSql('ALTER TABLE chill_main_address DROP steps');
$this->addSql('ALTER TABLE chill_main_address DROP buildingName');
$this->addSql('ALTER TABLE chill_main_address DROP flat');
$this->addSql('ALTER TABLE chill_main_address DROP distribution');
$this->addSql('ALTER TABLE chill_main_address DROP extra');
$this->addSql('ALTER TABLE chill_main_address DROP validTo');
$this->addSql('ALTER TABLE chill_main_address DROP point');
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add a AddressReference table for storing authoritative address data
*/
final class Version20210503085107 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add a AddressReference table for storing authoritative address data';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE SEQUENCE chill_main_address_reference_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_main_address_reference (id INT NOT NULL, postcode_id INT DEFAULT NULL, refId VARCHAR(255) NOT NULL, street VARCHAR(255) DEFAULT NULL, streetNumber VARCHAR(255) DEFAULT NULL, municipalityCode VARCHAR(255) DEFAULT NULL, source VARCHAR(255) DEFAULT NULL, point geometry(POINT,4326) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_CA6C1BD7EECBFDF1 ON chill_main_address_reference (postcode_id)');
$this->addSql('ALTER TABLE chill_main_address_reference ADD CONSTRAINT FK_CA6C1BD7EECBFDF1 FOREIGN KEY (postcode_id) REFERENCES chill_main_postal_code (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
$this->addSql('DROP SEQUENCE chill_main_address_reference_id_seq CASCADE');
$this->addSql('DROP TABLE chill_main_address_reference');
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add linkedToThirdParty field to Address
*/
final class Version20210505153727 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add linkedToThirdParty field to Address';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_address ADD linkedToThirdParty_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT FK_165051F6114B8DD9 FOREIGN KEY (linkedToThirdParty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_165051F6114B8DD9 ON chill_main_address (linkedToThirdParty_id)');
$this->addSql('
CREATE TABLE chill_main_address_legacy AS
TABLE chill_main_address;
');
$this->addSql('
WITH hydrated_addresses AS (
SELECT *, rank() OVER (PARTITION BY pa_a.person_id ORDER BY validfrom)
FROM chill_main_address AS aa JOIN chill_person_persons_to_addresses AS pa_a ON aa.id = pa_a.address_id
)
UPDATE chill_main_address AS b
SET validto = (
SELECT validfrom - INTERVAL \'1 DAY\'
FROM hydrated_addresses
WHERE hydrated_addresses.id = (
SELECT a1.id
FROM hydrated_addresses AS a1 JOIN hydrated_addresses AS a2 ON a2.person_id = a1.person_id AND a2.rank = (a1.rank-1)
WHERE a2.id = b.id
)
);
');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_address DROP CONSTRAINT FK_165051F6114B8DD9');
$this->addSql('DROP INDEX IDX_165051F6114B8DD9');
$this->addSql('ALTER TABLE chill_main_address DROP linkedToThirdParty_id');
$this->addSql('DROP TABLE IF EXISTS chill_main_address_legacy');
$this->addSql('
UPDATE chill_main_address
SET validto = null;
');
}
}

View File

@ -29,6 +29,7 @@ use Chill\PersonBundle\Entity\Person;
use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes; use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Doctrine\Model\Point;
/** /**
* Load people into database * Load people into database
@ -37,17 +38,17 @@ use Chill\MainBundle\Entity\Address;
* @author Marc Ducobu <marc@champs-libres.coop> * @author Marc Ducobu <marc@champs-libres.coop>
*/ */
class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{ {
use \Symfony\Component\DependencyInjection\ContainerAwareTrait; use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
protected $faker; protected $faker;
public function __construct() public function __construct()
{ {
$this->faker = \Faker\Factory::create('fr_FR'); $this->faker = \Faker\Factory::create('fr_FR');
} }
public function prepare() public function prepare()
{ {
//prepare days, month, years //prepare days, month, years
@ -56,57 +57,57 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
$this->years[] = $y; $this->years[] = $y;
$y = $y +1; $y = $y +1;
} while ($y >= 1990); } while ($y >= 1990);
$m = 1; $m = 1;
do { do {
$this->month[] = $m; $this->month[] = $m;
$m = $m +1; $m = $m +1;
} while ($m >= 12); } while ($m >= 12);
$d = 1; $d = 1;
do { do {
$this->day[] = $d; $this->day[] = $d;
$d = $d + 1; $d = $d + 1;
} while ($d <= 28); } while ($d <= 28);
} }
public function getOrder() public function getOrder()
{ {
return 10000; return 10000;
} }
public function load(ObjectManager $manager) public function load(ObjectManager $manager)
{ {
$this->loadRandPeople($manager); $this->loadRandPeople($manager);
$this->loadExpectedPeople($manager); $this->loadExpectedPeople($manager);
$manager->flush(); $manager->flush();
} }
public function loadExpectedPeople(ObjectManager $manager) public function loadExpectedPeople(ObjectManager $manager)
{ {
echo "loading expected people...\n"; echo "loading expected people...\n";
foreach ($this->peoples as $person) { foreach ($this->peoples as $person) {
$this->addAPerson($this->fillWithDefault($person), $manager); $this->addAPerson($this->fillWithDefault($person), $manager);
} }
} }
public function loadRandPeople(ObjectManager $manager) public function loadRandPeople(ObjectManager $manager)
{ {
echo "loading rand people...\n"; echo "loading rand people...\n";
$this->prepare(); $this->prepare();
$chooseLastNameOrTri = array('tri', 'tri', 'name', 'tri'); $chooseLastNameOrTri = array('tri', 'tri', 'name', 'tri');
$i = 0; $i = 0;
do { do {
$i++; $i++;
$sex = $this->genders[array_rand($this->genders)]; $sex = $this->genders[array_rand($this->genders)];
if ($chooseLastNameOrTri[array_rand($chooseLastNameOrTri)] === 'tri' ) { if ($chooseLastNameOrTri[array_rand($chooseLastNameOrTri)] === 'tri' ) {
$length = rand(2, 3); $length = rand(2, 3);
$lastName = ''; $lastName = '';
@ -117,13 +118,13 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
} else { } else {
$lastName = $this->lastNames[array_rand($this->lastNames)]; $lastName = $this->lastNames[array_rand($this->lastNames)];
} }
if ($sex === Person::MALE_GENDER) { if ($sex === Person::MALE_GENDER) {
$firstName = $this->firstNamesMale[array_rand($this->firstNamesMale)]; $firstName = $this->firstNamesMale[array_rand($this->firstNamesMale)];
} else { } else {
$firstName = $this->firstNamesFemale[array_rand($this->firstNamesFemale)]; $firstName = $this->firstNamesFemale[array_rand($this->firstNamesFemale)];
} }
// add an address on 80% of the created people // add an address on 80% of the created people
if (rand(0,100) < 80) { if (rand(0,100) < 80) {
$address = $this->getRandomAddress(); $address = $this->getRandomAddress();
@ -137,7 +138,7 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
} else { } else {
$address = null; $address = null;
} }
$person = array( $person = array(
'FirstName' => $firstName, 'FirstName' => $firstName,
'LastName' => $lastName, 'LastName' => $lastName,
@ -147,15 +148,15 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
'Address' => $address, 'Address' => $address,
'maritalStatus' => $this->maritalStatusRef[array_rand($this->maritalStatusRef)] 'maritalStatus' => $this->maritalStatusRef[array_rand($this->maritalStatusRef)]
); );
$this->addAPerson($this->fillWithDefault($person), $manager); $this->addAPerson($this->fillWithDefault($person), $manager);
} while ($i <= 100); } while ($i <= 100);
} }
/** /**
* fill a person array with default value * fill a person array with default value
* *
* @param string[] $specific * @param string[] $specific
*/ */
private function fillWithDefault(array $specific) private function fillWithDefault(array $specific)
@ -171,10 +172,10 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
'Address' => null 'Address' => null
), $specific); ), $specific);
} }
/** /**
* create a new person from array data * create a new person from array data
* *
* @param array $person * @param array $person
* @param ObjectManager $manager * @param ObjectManager $manager
* @throws \Exception * @throws \Exception
@ -200,35 +201,51 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
$this->addAccompanyingPeriods($p, $value, $manager); $this->addAccompanyingPeriods($p, $value, $manager);
break; break;
} }
//try to add the data using the setSomething function, //try to add the data using the setSomething function,
// if not possible, fallback to addSomething function // if not possible, fallback to addSomething function
if (method_exists($p, 'set'.$key)) { if (method_exists($p, 'set'.$key)) {
call_user_func(array($p, 'set'.$key), $value); call_user_func(array($p, 'set'.$key), $value);
} elseif (method_exists($p, 'add'.$key)) { } elseif (method_exists($p, 'add'.$key)) {
// if we have a "addSomething", we may have multiple items to add // if we have a "addSomething", we may have multiple items to add
// so, we set the value in an array if it is not an array, and // so, we set the value in an array if it is not an array, and
// will call the function addSomething multiple times // will call the function addSomething multiple times
if (!is_array($value)) { if (!is_array($value)) {
$value = array($value); $value = array($value);
} }
foreach($value as $v) { foreach($value as $v) {
if ($v !== NULL) { if ($v !== NULL) {
call_user_func(array($p, 'add'.$key), $v); call_user_func(array($p, 'add'.$key), $v);
} }
} }
} }
} }
$manager->persist($p); $manager->persist($p);
echo "add person'".$p->__toString()."'\n"; echo "add person'".$p->__toString()."'\n";
} }
/** /**
* Creata a random address * Create a random point
* *
* @return Point
*/
private function getRandomPoint()
{
$lonBrussels = 4.35243;
$latBrussels = 50.84676;
$lon = $lonBrussels + 0.01 * rand(-5, 5);
$lat = $latBrussels + 0.01 * rand(-5, 5);
return Point::fromLonLat($lon, $lat);
}
/**
* Create a random address
*
* @return Address * @return Address
*/ */
private function getRandomAddress() private function getRandomAddress()
@ -238,13 +255,16 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
->setStreetAddress2( ->setStreetAddress2(
rand(0,9) > 5 ? $this->faker->streetAddress : '' rand(0,9) > 5 ? $this->faker->streetAddress : ''
) )
->setPoint(
rand(0,9) > 5 ? $this->getRandomPoint() : NULL
)
->setPostcode($this->getReference( ->setPostcode($this->getReference(
LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)]
)) ))
->setValidFrom($this->faker->dateTimeBetween('-5 years')) ->setValidFrom($this->faker->dateTimeBetween('-5 years'))
; ;
} }
private function getCountry($countryCode) private function getCountry($countryCode)
{ {
if ($countryCode === NULL) { if ($countryCode === NULL) {
@ -257,30 +277,30 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
private $maritalStatusRef = ['ms_single', 'ms_married', 'ms_widow', 'ms_separat', private $maritalStatusRef = ['ms_single', 'ms_married', 'ms_widow', 'ms_separat',
'ms_divorce', 'ms_legalco', 'ms_unknown']; 'ms_divorce', 'ms_legalco', 'ms_unknown'];
private $firstNamesMale = array("Jean", "Mohamed", "Alfred", "Robert", "Justin", "Brian", private $firstNamesMale = array("Jean", "Mohamed", "Alfred", "Robert", "Justin", "Brian",
"Compère", "Jean-de-Dieu", "Charles", "Pierre", "Luc", "Mathieu", "Alain", "Etienne", "Eric", "Compère", "Jean-de-Dieu", "Charles", "Pierre", "Luc", "Mathieu", "Alain", "Etienne", "Eric",
"Corentin", "Gaston", "Spirou", "Fantasio", "Mahmadou", "Mohamidou", "Vursuv", "Youssef" ); "Corentin", "Gaston", "Spirou", "Fantasio", "Mahmadou", "Mohamidou", "Vursuv", "Youssef" );
private $firstNamesFemale = array("Svedana", "Sevlatina", "Irène", "Marcelle", private $firstNamesFemale = array("Svedana", "Sevlatina", "Irène", "Marcelle",
"Corentine", "Alfonsine", "Caroline", "Solange", "Gostine", "Fatoumata", "Nicole", "Corentine", "Alfonsine", "Caroline", "Solange", "Gostine", "Fatoumata", "Nicole",
"Groseille", "Chana", "Oxana", "Ivana", "Julie", "Tina", "Adèle" ); "Groseille", "Chana", "Oxana", "Ivana", "Julie", "Tina", "Adèle" );
private $lastNames = array("Diallo", "Bah", "Gaillot", "Martin"); private $lastNames = array("Diallo", "Bah", "Gaillot", "Martin");
private $lastNamesTrigrams = array("fas", "tré", "hu", 'blart', 'van', 'der', 'lin', 'den', private $lastNamesTrigrams = array("fas", "tré", "hu", 'blart', 'van', 'der', 'lin', 'den',
'ta', 'mi', 'net', 'gna', 'bol', 'sac', 'ré', 'jo', 'du', 'pont', 'cas', 'tor', 'rob', 'al', 'ta', 'mi', 'net', 'gna', 'bol', 'sac', 'ré', 'jo', 'du', 'pont', 'cas', 'tor', 'rob', 'al',
'ma', 'gone', 'car',"fu", "ka", "lot", "no", "va", "du", "bu", "su", "jau", "tte", 'sir', 'ma', 'gone', 'car',"fu", "ka", "lot", "no", "va", "du", "bu", "su", "jau", "tte", 'sir',
"lo", 'to', "cho", "car", 'mo','zu', 'qi', 'mu'); "lo", 'to', "cho", "car", 'mo','zu', 'qi', 'mu');
private $genders = array(Person::MALE_GENDER, Person::FEMALE_GENDER); private $genders = array(Person::MALE_GENDER, Person::FEMALE_GENDER);
private $years = array(); private $years = array();
private $month = array(); private $month = array();
private $day = array(); private $day = array();
private $peoples = array( private $peoples = array(
array( array(
'LastName' => "Depardieu", 'LastName' => "Depardieu",
@ -362,21 +382,21 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
'maritalStatus' => 'ms_legalco' 'maritalStatus' => 'ms_legalco'
), ),
); );
private function addAccompanyingPeriods(Person $person, array $periods, ObjectManager $manager) private function addAccompanyingPeriods(Person $person, array $periods, ObjectManager $manager)
{ {
foreach ($periods as $period) { foreach ($periods as $period) {
echo "adding new past Accompanying Period..\n"; echo "adding new past Accompanying Period..\n";
/** @var AccompanyingPeriod $accompanyingPeriod */ /** @var AccompanyingPeriod $accompanyingPeriod */
$accompanyingPeriod = new AccompanyingPeriod(new \DateTime($period['from'])); $accompanyingPeriod = new AccompanyingPeriod(new \DateTime($period['from']));
$accompanyingPeriod $accompanyingPeriod
->setClosingDate(new \DateTime($period['to'])) ->setClosingDate(new \DateTime($period['to']))
->setRemark($period['remark']) ->setRemark($period['remark'])
; ;
$person->addAccompanyingPeriod($accompanyingPeriod); $person->addAccompanyingPeriod($accompanyingPeriod);
} }
} }

View File

@ -0,0 +1,70 @@
<?php
namespace Chill\PersonBundle\Entity\Household;
use Chill\PersonBundle\Repository\Household\HouseholdRepository;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Chill\MainBundle\Entity\Address;
/**
* @ORM\Entity(repositoryClass=HouseholdRepository::class)
*/
class Household
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
public function getId(): ?int
{
return $this->id;
}
/**
* Addresses
* @var Collection
*
* @ORM\ManyToMany(
* targetEntity="Chill\MainBundle\Entity\Address",
* cascade={"persist", "remove", "merge", "detach"})
* @ORM\JoinTable(name="chill_person_household_to_addresses")
* @ORM\OrderBy({"validFrom" = "DESC"})
*/
private $addresses;
/**
* @param Address $address
* @return $this
*/
public function addAddress(Address $address)
{
$this->addresses[] = $address;
return $this;
}
/**
* @param Address $address
*/
public function removeAddress(Address $address)
{
$this->addresses->removeElement($address);
}
/**
* By default, the addresses are ordered by date, descending (the most
* recent first)
*
* @return \Chill\MainBundle\Entity\Address[]
*/
public function getAddresses()
{
return $this->addresses;
}
}

View File

@ -0,0 +1,153 @@
<?php
namespace Chill\PersonBundle\Entity\Household;
use Doctrine\ORM\Mapping as ORM;
use Chill\PersonBundle\Repository\Household\HouseholdMembersRepository;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Household\Household;
/**
* @ORM\Entity(repositoryClass=HouseholdMembersRepository::class)
*/
class HouseholdMembers
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $position;
/**
* @ORM\Column(type="date")
*/
private $startDate;
/**
* @ORM\Column(type="date")
*/
private $endDate;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $comment;
/**
* @ORM\Column(type="boolean")
*/
private $sharedHousehold;
/**
*
* @var Person
* @ORM\ManyToOne(
* targetEntity="\Chill\PersonBundle\Entity\Person"
* )
*/
private $person;
/**
*
* @var Household
* @ORM\ManyToOne(
* targetEntity="\Chill\PersonBundle\Entity\Household\Household"
* )
*/
private $household;
public function getId(): ?int
{
return $this->id;
}
public function getPosition(): ?string
{
return $this->position;
}
public function setPosition(?string $position): self
{
$this->position = $position;
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->startDate;
}
public function setStartDate(\DateTimeInterface $startDate): self
{
$this->startDate = $startDate;
return $this;
}
public function getEndDate(): ?\DateTimeInterface
{
return $this->endDate;
}
public function setEndDate(\DateTimeInterface $endDate): self
{
$this->endDate = $endDate;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
}
public function setComment(?string $comment): self
{
$this->comment = $comment;
return $this;
}
public function getSharedHousehold(): ?bool
{
return $this->sharedHousehold;
}
public function setSharedHousehold(bool $sharedHousehold): self
{
$this->sharedHousehold = $sharedHousehold;
return $this;
}
public function getPerson(): ?Person
{
return $this->person;
}
public function setPerson(?Person $person): self
{
$this->person = $person;
return $this;
}
public function getHousehold(): ?Household
{
return $this->household;
}
public function setHousehold(?Household $household): self
{
$this->household = $household;
return $this;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Chill\PersonBundle\Repository\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMembers;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method HouseholdMembers|null find($id, $lockMode = null, $lockVersion = null)
* @method HouseholdMembers|null findOneBy(array $criteria, array $orderBy = null)
* @method HouseholdMembers[] findAll()
* @method HouseholdMembers[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class HouseholdMembersRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, HouseholdMembers::class);
}
// /**
// * @return HouseholdMembers[] Returns an array of HouseholdMembers objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('h')
->andWhere('h.exampleField = :val')
->setParameter('val', $value)
->orderBy('h.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?HouseholdMembers
{
return $this->createQueryBuilder('h')
->andWhere('h.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View File

@ -0,0 +1,50 @@
<?php
namespace Chill\PersonBundle\Repository\Household;
use Chill\PersonBundle\Entity\Household\Household;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Household|null find($id, $lockMode = null, $lockVersion = null)
* @method Household|null findOneBy(array $criteria, array $orderBy = null)
* @method Household[] findAll()
* @method Household[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class HouseholdRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Household::class);
}
// /**
// * @return Household[] Returns an array of Household objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('h')
->andWhere('h.exampleField = :val')
->setParameter('val', $value)
->orderBy('h.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?Household
{
return $this->createQueryBuilder('h')
->andWhere('h.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View File

@ -21,17 +21,17 @@
{% block title 'Update address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) %} {% block title 'Update address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) %}
{% block personcontent %} {% block personcontent %}
<h1>{{ 'Update address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}</h1> <h1>{{ 'Update address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}</h1>
{{ form_start(form) }} {{ form_start(form) }}
{{ form_row(form.isNoAddress) }} {{ form_row(form.isNoAddress) }}
{{ form_row(form.streetAddress1) }} {{ form_row(form.street) }}
{{ form_row(form.streetAddress2) }} {{ form_row(form.streetNumber) }}
{{ form_row(form.postCode) }} {{ form_row(form.postCode) }}
{{ form_row(form.validFrom) }} {{ form_row(form.validFrom) }}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path('chill_person_address_list', { 'person_id' : person.id } ) }}" class="sc-button bt-cancel"> <a href="{{ path('chill_person_address_list', { 'person_id' : person.id } ) }}" class="sc-button bt-cancel">
@ -42,7 +42,7 @@
{{ form_row(form.submit, { 'attr' : { 'class': 'sc-button bt-save' }, 'label': 'Save' } ) }} {{ form_row(form.submit, { 'attr' : { 'class': 'sc-button bt-save' }, 'label': 'Save' } ) }}
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}
{% endblock personcontent %} {% endblock personcontent %}

View File

@ -23,9 +23,9 @@
{% block title %}{{ 'Addresses\'history for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}{% endblock %} {% block title %}{{ 'Addresses\'history for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}{% endblock %}
{% block personcontent %} {% block personcontent %}
<h1>{{ 'Addresses\'history for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}</h1> <h1>{{ 'Addresses\'history for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}</h1>
<table class="records_list"> <table class="records_list">
<thead> <thead>
<tr> <tr>
@ -48,11 +48,11 @@
{% for address in person.addresses %} {% for address in person.addresses %}
<tr> <tr>
<td><strong>{{ 'Since %date%'|trans( { '%date%' : address.validFrom|format_date('long') } ) }}</strong></td> <td><strong>{{ 'Since %date%'|trans( { '%date%' : address.validFrom|format_date('long') } ) }}</strong></td>
<td> <td>
{{ address_macros._render(address, { 'with_valid_from' : false, 'has_no_address': true } ) }} {{ address_macros._render(address, { 'with_valid_from' : false, 'has_no_address': true } ) }}
</td> </td>
<td> <td>
<ul class="record_actions"> <ul class="record_actions">
<li> <li>
@ -61,11 +61,17 @@
</ul> </ul>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
<ul class="record_actions"> <ul class="record_actions">
<li class="cancel"> <li class="cancel">
<a href="{{ path('chill_person_view', { 'person_id' : person.id } ) }}" class="sc-button bt-cancel"> <a href="{{ path('chill_person_view', { 'person_id' : person.id } ) }}" class="sc-button bt-cancel">
@ -73,10 +79,11 @@
</a> </a>
</li> </li>
<li> <li>
<a href="{{ path('chill_person_address_new', { 'person_id' : person.id } ) }}" class="sc-button bt-create"> <a href="{{ path('chill_person_address_new', { 'person_id' : person.id } ) }}" class="sc-button bt-create">
{{ 'Add an address'|trans }} {{ 'Add an address'|trans }}
</a> </a>
</li> </li>
</ul> </ul>
{% endblock personcontent %} {% endblock personcontent %}

View File

@ -21,21 +21,21 @@
{% block title %}{{ 'New address for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %} {% block title %}{{ 'New address for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %}
{% block personcontent %} {% block personcontent %}
<h1>{{ 'New address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}</h1> <h1>{{ 'New address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}</h1>
{{ form_start(form) }} {{ form_start(form) }}
{{ form_row(form.isNoAddress) }} {{ form_row(form.isNoAddress) }}
{{ form_row(form.streetAddress1) }} {{ form_row(form.street) }}
{{ form_errors(form.streetAddress1) }} {{ form_errors(form.street) }}
{{ form_row(form.streetAddress2) }} {{ form_row(form.streetNumber) }}
{{ form_errors(form.streetAddress2) }} {{ form_errors(form.streetNumber) }}
{{ form_row(form.postCode) }} {{ form_row(form.postCode) }}
{{ form_errors(form.postCode) }} {{ form_errors(form.postCode) }}
{{ form_row(form.validFrom) }} {{ form_row(form.validFrom) }}
{{ form_errors(form.validFrom) }} {{ form_errors(form.validFrom) }}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path('chill_person_address_list', { 'person_id' : person.id } ) }}" class="sc-button bt-cancel"> <a href="{{ path('chill_person_address_list', { 'person_id' : person.id } ) }}" class="sc-button bt-cancel">
@ -46,7 +46,7 @@
{{ form_row(form.submit, { 'attr' : { 'class': 'sc-button bt-create' }, 'label': 'Create' } ) }} {{ form_row(form.submit, { 'attr' : { 'class': 'sc-button bt-create' }, 'label': 'Create' } ) }}
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}
{% endblock personcontent %} {% endblock personcontent %}

View File

@ -7,6 +7,6 @@ module.exports = function(encore, entries)
encore.addAliases({ encore.addAliases({
ChillPersonAssets: __dirname + '/Resources/public' ChillPersonAssets: __dirname + '/Resources/public'
}); });
encore.addEntry('accompanying_course', __dirname + '/Resources/public/js/AccompanyingCourse/index.js'); encore.addEntry('accompanying_course', __dirname + '/Resources/public/js/AccompanyingCourse/index.js');
}; };

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Create Household and HouseholdMembers tables
*/
final class Version20210505093408 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create Household and HouseholdMembers tables';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE SEQUENCE Household_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE HouseholdMembers_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE Household (id INT NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE TABLE HouseholdMembers (id INT NOT NULL, person_id INT DEFAULT NULL, household_id INT DEFAULT NULL, position VARCHAR(255) DEFAULT NULL, startDate DATE NOT NULL, endDate DATE NOT NULL, comment VARCHAR(255) DEFAULT NULL, sharedHousehold BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_4D1FB288217BBB47 ON HouseholdMembers (person_id)');
$this->addSql('CREATE INDEX IDX_4D1FB288E79FF843 ON HouseholdMembers (household_id)');
$this->addSql('ALTER TABLE HouseholdMembers ADD CONSTRAINT FK_4D1FB288217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE HouseholdMembers ADD CONSTRAINT FK_4D1FB288E79FF843 FOREIGN KEY (household_id) REFERENCES Household (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE HouseholdMembers DROP CONSTRAINT FK_4D1FB288E79FF843');
$this->addSql('ALTER TABLE HouseholdMembers DROP CONSTRAINT FK_4D1FB288217BBB47');
$this->addSql('DROP SEQUENCE Household_id_seq CASCADE');
$this->addSql('DROP SEQUENCE HouseholdMembers_id_seq CASCADE');
$this->addSql('DROP TABLE Household');
$this->addSql('DROP TABLE HouseholdMembers');
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add a household_to_address table
*/
final class Version20210505154316 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add a household_to_addresses table';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE chill_person_household_to_addresses (household_id INT NOT NULL, address_id INT NOT NULL, PRIMARY KEY(household_id, address_id))');
$this->addSql('CREATE INDEX IDX_7109483E79FF843 ON chill_person_household_to_addresses (household_id)');
$this->addSql('CREATE INDEX IDX_7109483F5B7AF75 ON chill_person_household_to_addresses (address_id)');
$this->addSql('ALTER TABLE chill_person_household_to_addresses ADD CONSTRAINT FK_7109483E79FF843 FOREIGN KEY (household_id) REFERENCES Household (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_household_to_addresses ADD CONSTRAINT FK_7109483F5B7AF75 FOREIGN KEY (address_id) REFERENCES chill_main_address (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE chill_person_household_to_addresses');
}
}