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(
'dbal' => [
'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 Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Doctrine\Model\Point;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
/**
* Address
@ -28,14 +30,14 @@ class Address
*
* @ORM\Column(type="string", length=255)
*/
private $streetAddress1 = '';
private $street = '';
/**
* @var string
*
* @ORM\Column(type="string", length=255)
*/
private $streetAddress2 = '';
private $streetNumber = '';
/**
* @var PostalCode
@ -44,6 +46,55 @@ class Address
*/
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
* of address. By default, the current date.
@ -54,6 +105,16 @@ class Address
*/
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, ...
*
@ -61,6 +122,25 @@ class Address
*/
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
*
@ -73,7 +153,6 @@ class Address
$this->validFrom = new \DateTime();
}
/**
* Get id
*
@ -85,7 +164,7 @@ class Address
}
/**
* Set streetAddress1
* Set streetAddress1 (legacy function)
*
* @param string $streetAddress1
*
@ -93,23 +172,23 @@ class Address
*/
public function setStreetAddress1($streetAddress1)
{
$this->streetAddress1 = $streetAddress1 === NULL ? '' : $streetAddress1;
$this->street = $streetAddress1 === NULL ? '' : $streetAddress1;
return $this;
}
/**
* Get streetAddress1
* Get streetAddress1 (legacy function)
*
* @return string
*/
public function getStreetAddress1()
{
return $this->streetAddress1;
return $this->street;
}
/**
* Set streetAddress2
* Set streetAddress2 (legacy function)
*
* @param string $streetAddress2
*
@ -117,19 +196,19 @@ class Address
*/
public function setStreetAddress2($streetAddress2)
{
$this->streetAddress2 = $streetAddress2 === NULL ? '' : $streetAddress2;
$this->streetNumber = $streetAddress2 === NULL ? '' : $streetAddress2;
return $this;
}
/**
* Get streetAddress2
* Get streetAddress2 (legacy function)
*
* @return string
*/
public function getStreetAddress2()
{
return $this->streetAddress2;
return $this->streetNumber;
}
/**
@ -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

@ -45,10 +45,10 @@ class AddressType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('streetAddress1', TextType::class, array(
->add('street', TextType::class, array(
'required' => !$options['has_no_address'] // true if has no address is false
))
->add('streetAddress2', TextType::class, array(
->add('streetNumber', TextType::class, array(
'required' => false
))
->add('postCode', PostalCodeType::class, array(

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>
{% endif %}
<div class="chill_address_address">
{% if address.streetAddress1 is not empty %}<p class="street street1">{{ address.streetAddress1 }}</p>{% endif %}
{% if address.streetAddress2 is not empty %}<p class="street street2">{{ address.streetAddress2 }}</p>{% endif %}
{% if address.street is not empty %}<p class="street street1">{{ address.street }}</p>{% endif %}
{% if address.streetNumber is not empty %}<p class="street street2">{{ address.streetNumber }}</p>{% endif %}
{% 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="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 Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Doctrine\Model\Point;
/**
* Load people into database
@ -226,8 +227,24 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
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
*/
@ -238,6 +255,9 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
->setStreetAddress2(
rand(0,9) > 5 ? $this->faker->streetAddress : ''
)
->setPoint(
rand(0,9) > 5 ? $this->getRandomPoint() : NULL
)
->setPostcode($this->getReference(
LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)]
))

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

@ -27,8 +27,8 @@
{{ form_start(form) }}
{{ form_row(form.isNoAddress) }}
{{ form_row(form.streetAddress1) }}
{{ form_row(form.streetAddress2) }}
{{ form_row(form.street) }}
{{ form_row(form.streetNumber) }}
{{ form_row(form.postCode) }}
{{ form_row(form.validFrom) }}

View File

@ -61,11 +61,17 @@
</ul>
</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
<ul class="record_actions">
<li class="cancel">
<a href="{{ path('chill_person_view', { 'person_id' : person.id } ) }}" class="sc-button bt-cancel">
@ -73,6 +79,7 @@
</a>
</li>
<li>
<a href="{{ path('chill_person_address_new', { 'person_id' : person.id } ) }}" class="sc-button bt-create">
{{ 'Add an address'|trans }}
</a>

View File

@ -27,10 +27,10 @@
{{ form_start(form) }}
{{ form_row(form.isNoAddress) }}
{{ form_row(form.streetAddress1) }}
{{ form_errors(form.streetAddress1) }}
{{ form_row(form.streetAddress2) }}
{{ form_errors(form.streetAddress2) }}
{{ form_row(form.street) }}
{{ form_errors(form.street) }}
{{ form_row(form.streetNumber) }}
{{ form_errors(form.streetNumber) }}
{{ form_row(form.postCode) }}
{{ form_errors(form.postCode) }}
{{ form_row(form.validFrom) }}

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');
}
}