mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Issue316 addresses search by postal code
This commit is contained in:
parent
51bbcab878
commit
938720be52
@ -11,6 +11,9 @@ and this project adheres to
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
<!-- write down unreleased development here -->
|
<!-- write down unreleased development here -->
|
||||||
|
* [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
|
||||||
|
* [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
|
||||||
|
|
||||||
* [person search] fix bug when using birthdate after and birthdate before
|
* [person search] fix bug when using birthdate after and birthdate before
|
||||||
* [person search] increase pertinence when lastname begins with search pattern
|
* [person search] increase pertinence when lastname begins with search pattern
|
||||||
|
|
||||||
|
@ -12,14 +12,66 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Controller;
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||||
|
use Chill\MainBundle\Repository\AddressReferenceRepository;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use function trim;
|
||||||
|
|
||||||
|
final class AddressReferenceAPIController extends ApiController
|
||||||
|
{
|
||||||
|
private AddressReferenceRepository $addressReferenceRepository;
|
||||||
|
|
||||||
|
private PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
|
public function __construct(AddressReferenceRepository $addressReferenceRepository, PaginatorFactory $paginatorFactory)
|
||||||
|
{
|
||||||
|
$this->addressReferenceRepository = $addressReferenceRepository;
|
||||||
|
$this->paginatorFactory = $paginatorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AddressReferenceAPIController.
|
* @Route("/api/1.0/main/address-reference/by-postal-code/{id}/search.json")
|
||||||
*/
|
*/
|
||||||
class AddressReferenceAPIController extends ApiController
|
public function search(PostalCode $postalCode, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||||
|
|
||||||
|
if (!$request->query->has('q')) {
|
||||||
|
throw new BadRequestHttpException('You must supply a "q" parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = $request->query->get('q');
|
||||||
|
|
||||||
|
if ('' === trim($pattern)) {
|
||||||
|
throw new BadRequestHttpException('the search pattern is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nb = $this->addressReferenceRepository->countByPostalCodePattern($postalCode, $pattern);
|
||||||
|
$paginator = $this->paginatorFactory->create($nb);
|
||||||
|
$addresses = $this->addressReferenceRepository->findByPostalCodePattern(
|
||||||
|
$postalCode,
|
||||||
|
$pattern,
|
||||||
|
false,
|
||||||
|
$paginator->getCurrentPageFirstItemNumber(),
|
||||||
|
$paginator->getItemsPerPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json(
|
||||||
|
new Collection($addresses, $paginator),
|
||||||
|
Response::HTTP_OK,
|
||||||
|
[],
|
||||||
|
[AbstractNormalizer::GROUPS => ['read']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected function customizeQuery(string $action, Request $request, $qb): void
|
protected function customizeQuery(string $action, Request $request, $qb): void
|
||||||
{
|
{
|
||||||
if ($request->query->has('postal_code')) {
|
if ($request->query->has('postal_code')) {
|
||||||
|
@ -12,13 +12,80 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Controller;
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
use Chill\MainBundle\Repository\CountryRepository;
|
||||||
|
use Chill\MainBundle\Repository\PostalCodeRepository;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
|
||||||
|
final class PostalCodeAPIController extends ApiController
|
||||||
|
{
|
||||||
|
private CountryRepository $countryRepository;
|
||||||
|
|
||||||
|
private PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
|
private PostalCodeRepository $postalCodeRepository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
CountryRepository $countryRepository,
|
||||||
|
PostalCodeRepository $postalCodeRepository,
|
||||||
|
PaginatorFactory $paginatorFactory
|
||||||
|
) {
|
||||||
|
$this->countryRepository = $countryRepository;
|
||||||
|
$this->postalCodeRepository = $postalCodeRepository;
|
||||||
|
$this->paginatorFactory = $paginatorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class PostalCodeAPIController.
|
* @Route("/api/1.0/main/postal-code/search.json")
|
||||||
*/
|
*/
|
||||||
class PostalCodeAPIController extends ApiController
|
public function search(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||||
|
|
||||||
|
if (!$request->query->has('q')) {
|
||||||
|
throw new BadRequestHttpException('You must supply a "q" parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = $request->query->get('q');
|
||||||
|
|
||||||
|
if ('' === trim($pattern)) {
|
||||||
|
throw new BadRequestHttpException('the search pattern is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->query->has('country')) {
|
||||||
|
$country = $this->countryRepository->find($request->query->getInt('country'));
|
||||||
|
|
||||||
|
if (null === $country) {
|
||||||
|
throw new NotFoundHttpException('country not found');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$country = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nb = $this->postalCodeRepository->countByPattern($pattern, $country);
|
||||||
|
$paginator = $this->paginatorFactory->create($nb);
|
||||||
|
$codes = $this->postalCodeRepository->findByPattern(
|
||||||
|
$pattern,
|
||||||
|
$country,
|
||||||
|
$paginator->getCurrentPageFirstItemNumber(),
|
||||||
|
$paginator->getItemsPerPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json(
|
||||||
|
new Collection($codes, $paginator),
|
||||||
|
Response::HTTP_OK,
|
||||||
|
[],
|
||||||
|
[AbstractNormalizer::GROUPS => ['read']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected function customizeQuery(string $action, Request $request, $qb): void
|
protected function customizeQuery(string $action, Request $request, $qb): void
|
||||||
{
|
{
|
||||||
if ($request->query->has('country')) {
|
if ($request->query->has('country')) {
|
||||||
|
@ -22,6 +22,15 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
*/
|
*/
|
||||||
class AddressReference
|
class AddressReference
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* This is an internal column which is populated by database.
|
||||||
|
*
|
||||||
|
* This column will ease the search operations
|
||||||
|
*
|
||||||
|
* @ORM\Column(type="text", options={"default": ""})
|
||||||
|
*/
|
||||||
|
private string $addressCanonical = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
* @ORM\GeneratedValue
|
* @ORM\GeneratedValue
|
||||||
|
@ -29,6 +29,15 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
*/
|
*/
|
||||||
class PostalCode
|
class PostalCode
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* This is an internal column which is populated by database.
|
||||||
|
*
|
||||||
|
* This column will ease the search operations
|
||||||
|
*
|
||||||
|
* @ORM\Column(type="text", options={"default": ""})
|
||||||
|
*/
|
||||||
|
private string $canonical = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Point
|
* @var Point
|
||||||
*
|
*
|
||||||
|
@ -12,17 +12,29 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Repository;
|
namespace Chill\MainBundle\Repository;
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\AddressReference;
|
use Chill\MainBundle\Entity\AddressReference;
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Search\SearchApiQuery;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMapping;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
use RuntimeException;
|
||||||
|
use function explode;
|
||||||
|
use function implode;
|
||||||
|
use function strtr;
|
||||||
|
use function trim;
|
||||||
|
|
||||||
final class AddressReferenceRepository implements ObjectRepository
|
final class AddressReferenceRepository implements ObjectRepository
|
||||||
{
|
{
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(AddressReference::class);
|
$this->repository = $entityManager->getRepository(AddressReference::class);
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countAll(): int
|
public function countAll(): int
|
||||||
@ -33,6 +45,18 @@ final class AddressReferenceRepository implements ObjectRepository
|
|||||||
return $qb->getQuery()->getSingleScalarResult();
|
return $qb->getQuery()->getSingleScalarResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countByPostalCodePattern(PostalCode $postalCode, string $pattern): int
|
||||||
|
{
|
||||||
|
$query = $this->buildQueryByPostalCodePattern($postalCode, $pattern);
|
||||||
|
$sql = $query->buildQuery(true);
|
||||||
|
$rsm = new ResultSetMapping();
|
||||||
|
$rsm->addScalarResult('c', 'c');
|
||||||
|
|
||||||
|
$nq = $this->entityManager->createNativeQuery($sql, $rsm)->setParameters($query->buildParameters(true));
|
||||||
|
|
||||||
|
return (int) $nq->getSingleResult()['c'];
|
||||||
|
}
|
||||||
|
|
||||||
public function find($id, $lockMode = null, $lockVersion = null): ?AddressReference
|
public function find($id, $lockMode = null, $lockVersion = null): ?AddressReference
|
||||||
{
|
{
|
||||||
return $this->repository->find($id, $lockMode, $lockVersion);
|
return $this->repository->find($id, $lockMode, $lockVersion);
|
||||||
@ -57,6 +81,33 @@ final class AddressReferenceRepository implements ObjectRepository
|
|||||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return AddressReference[]|array
|
||||||
|
*/
|
||||||
|
public function findByPostalCodePattern(PostalCode $postalCode, string $pattern, bool $simplify = false, int $start = 0, int $limit = 50): array
|
||||||
|
{
|
||||||
|
$query = $this->buildQueryByPostalCodePattern($postalCode, $pattern);
|
||||||
|
|
||||||
|
if (!$simplify) {
|
||||||
|
$rsm = new ResultSetMappingBuilder($this->entityManager);
|
||||||
|
$rsm->addRootEntityFromClassMetadata(AddressReference::class, 'cma');
|
||||||
|
$query->addSelectClause($rsm->generateSelectClause());
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = strtr(
|
||||||
|
$query->buildQuery() . 'ORDER BY pertinence DESC, lpad(streetnumber, 10, \'0\') ASC OFFSET ? LIMIT ? ',
|
||||||
|
// little hack for adding sql method to point
|
||||||
|
['cma.point AS point' => 'ST_AsGeojson(cma.point) AS point']
|
||||||
|
);
|
||||||
|
$parameters = [...$query->buildParameters(), $start, $limit];
|
||||||
|
|
||||||
|
return $this->entityManager->createNativeQuery($sql, $rsm)
|
||||||
|
->setParameters($parameters)
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?AddressReference
|
public function findOneBy(array $criteria, ?array $orderBy = null): ?AddressReference
|
||||||
{
|
{
|
||||||
return $this->repository->findOneBy($criteria, $orderBy);
|
return $this->repository->findOneBy($criteria, $orderBy);
|
||||||
@ -66,4 +117,44 @@ final class AddressReferenceRepository implements ObjectRepository
|
|||||||
{
|
{
|
||||||
return AddressReference::class;
|
return AddressReference::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildQueryByPostalCodePattern(PostalCode $postalCode, string $pattern): SearchApiQuery
|
||||||
|
{
|
||||||
|
$pattern = trim($pattern);
|
||||||
|
|
||||||
|
if ('' === $pattern) {
|
||||||
|
throw new RuntimeException('the search pattern must not be empty');
|
||||||
|
}
|
||||||
|
$query = new SearchApiQuery();
|
||||||
|
|
||||||
|
$query
|
||||||
|
->setFromClause('chill_main_address_reference cma')
|
||||||
|
->andWhereClause('postcode_id = ?', [$postalCode->getId()]);
|
||||||
|
|
||||||
|
$pertinenceClause = ['STRICT_WORD_SIMILARITY(addresscanonical, UNACCENT(?))'];
|
||||||
|
$pertinenceArgs = [$pattern];
|
||||||
|
$orWhere = ['addresscanonical %>> UNACCENT(?)'];
|
||||||
|
$orWhereArgs = [$pattern];
|
||||||
|
|
||||||
|
foreach (explode(' ', $pattern) as $part) {
|
||||||
|
$part = trim($part);
|
||||||
|
|
||||||
|
if ('' === $part) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orWhere[] = "addresscanonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
|
||||||
|
$orWhereArgs[] = $part;
|
||||||
|
$pertinenceClause[] =
|
||||||
|
"(EXISTS (SELECT 1 FROM unnest(string_to_array(addresscanonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int";
|
||||||
|
$pertinenceClause[] =
|
||||||
|
'(addresscanonical LIKE UNACCENT(LOWER(?)))::int';
|
||||||
|
array_push($pertinenceArgs, $part, $part);
|
||||||
|
}
|
||||||
|
$query
|
||||||
|
->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs)
|
||||||
|
->andWhereClause(implode(' OR ', $orWhere), $orWhereArgs);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,18 +11,39 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Repository;
|
namespace Chill\MainBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Country;
|
||||||
use Chill\MainBundle\Entity\PostalCode;
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Search\SearchApiQuery;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMapping;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
final class PostalCodeRepository implements ObjectRepository
|
final class PostalCodeRepository implements ObjectRepository
|
||||||
{
|
{
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(PostalCode::class);
|
$this->repository = $entityManager->getRepository(PostalCode::class);
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countByPattern(string $pattern, ?Country $country): int
|
||||||
|
{
|
||||||
|
$query = $this->buildQueryByPattern($pattern, $country);
|
||||||
|
$sql = $query->buildQuery(true);
|
||||||
|
$rsm = new ResultSetMapping();
|
||||||
|
$rsm->addScalarResult('c', 'c');
|
||||||
|
|
||||||
|
$nq = $this->entityManager->createNativeQuery($sql, $rsm)
|
||||||
|
->setParameters($query->buildParameters(true));
|
||||||
|
|
||||||
|
return (int) $nq->getSingleResult()['c'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function find($id, $lockMode = null, $lockVersion = null): ?PostalCode
|
public function find($id, $lockMode = null, $lockVersion = null): ?PostalCode
|
||||||
@ -49,6 +70,26 @@ final class PostalCodeRepository implements ObjectRepository
|
|||||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findByPattern(string $pattern, ?Country $country, ?int $start = 0, ?int $limit = 50): array
|
||||||
|
{
|
||||||
|
$query = $this->buildQueryByPattern($pattern, $country);
|
||||||
|
|
||||||
|
$rsm = new ResultSetMappingBuilder($this->entityManager);
|
||||||
|
$rsm->addRootEntityFromClassMetadata(PostalCode::class, 'cmpc');
|
||||||
|
$query->addSelectClause($rsm->generateSelectClause());
|
||||||
|
|
||||||
|
$sql = strtr(
|
||||||
|
$query->buildQuery() . 'ORDER BY pertinence DESC, canonical ASC OFFSET ? LIMIT ? ',
|
||||||
|
// little hack for adding sql method to point
|
||||||
|
['cmpc.center AS center' => 'ST_AsGeojson(cmpc.center) AS center']
|
||||||
|
);
|
||||||
|
$parameters = [...$query->buildParameters(), $start, $limit];
|
||||||
|
|
||||||
|
return $this->entityManager->createNativeQuery($sql, $rsm)
|
||||||
|
->setParameters($parameters)
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?PostalCode
|
public function findOneBy(array $criteria, ?array $orderBy = null): ?PostalCode
|
||||||
{
|
{
|
||||||
return $this->repository->findOneBy($criteria, $orderBy);
|
return $this->repository->findOneBy($criteria, $orderBy);
|
||||||
@ -58,4 +99,48 @@ final class PostalCodeRepository implements ObjectRepository
|
|||||||
{
|
{
|
||||||
return PostalCode::class;
|
return PostalCode::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildQueryByPattern(string $pattern, ?Country $country): SearchApiQuery
|
||||||
|
{
|
||||||
|
$pattern = trim($pattern);
|
||||||
|
|
||||||
|
if ('' === $pattern) {
|
||||||
|
throw new RuntimeException('the search pattern must not be empty');
|
||||||
|
}
|
||||||
|
$query = new SearchApiQuery();
|
||||||
|
|
||||||
|
$query
|
||||||
|
->setFromClause('chill_main_postal_code cmpc')
|
||||||
|
->andWhereClause('cmpc.origin = 0');
|
||||||
|
|
||||||
|
if (null !== $country) {
|
||||||
|
$query->andWhereClause('cmpc.country_id = ?', [$country->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pertinenceClause = ['STRICT_WORD_SIMILARITY(canonical, UNACCENT(?))'];
|
||||||
|
$pertinenceArgs = [$pattern];
|
||||||
|
$orWhere = ['canonical %>> UNACCENT(?)'];
|
||||||
|
$orWhereArgs = [$pattern];
|
||||||
|
|
||||||
|
foreach (explode(' ', $pattern) as $part) {
|
||||||
|
$part = trim($part);
|
||||||
|
|
||||||
|
if ('' === $part) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orWhere[] = "canonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
|
||||||
|
$orWhereArgs[] = $part;
|
||||||
|
$pertinenceClause[] =
|
||||||
|
"(EXISTS (SELECT 1 FROM unnest(string_to_array(canonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int";
|
||||||
|
$pertinenceClause[] =
|
||||||
|
'(canonical LIKE UNACCENT(LOWER(?)))::int';
|
||||||
|
array_push($pertinenceArgs, $part, $part);
|
||||||
|
}
|
||||||
|
$query
|
||||||
|
->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs)
|
||||||
|
->andWhereClause(implode(' OR ', $orWhere), $orWhereArgs);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@ const fetchCountries = () => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint chill_api_single_postal_code__index
|
* Endpoint chill_api_single_postal_code__index
|
||||||
* method GET, get Country Object
|
* method GET, get Cities Object
|
||||||
|
* @params {object} a country object
|
||||||
* @returns {Promise} a promise containing all Postal Code objects filtered with country
|
* @returns {Promise} a promise containing all Postal Code objects filtered with country
|
||||||
*/
|
*/
|
||||||
const fetchCities = (country) => {
|
const fetchCities = (country) => {
|
||||||
@ -29,6 +30,40 @@ const fetchCities = (country) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint chill_main_postalcodeapi_search
|
||||||
|
* method GET, get Cities Object
|
||||||
|
* @params {string} search a search string
|
||||||
|
* @params {object} country a country object
|
||||||
|
* @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string
|
||||||
|
*/
|
||||||
|
const searchCities = (search, country) => {
|
||||||
|
const url = `/api/1.0/main/postal-code/search.json?q=${search}&country=${country.id}`;
|
||||||
|
return fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) { return response.json(); }
|
||||||
|
throw Error('Error with request resource response');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint chill_main_addressreferenceapi_search
|
||||||
|
* method GET, get AddressReference Object
|
||||||
|
* @params {string} search a search string
|
||||||
|
* @params {object} postalCode a postalCode object
|
||||||
|
* @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string
|
||||||
|
*/
|
||||||
|
const searchReferenceAddresses = (search, postalCode) => {
|
||||||
|
const url = `/api/1.0/main/address-reference/by-postal-code/${postalCode.id}/search.json?q=${search}`;
|
||||||
|
return fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) { return response.json(); }
|
||||||
|
throw Error('Error with request resource response');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint chill_api_single_address_reference__index
|
* Endpoint chill_api_single_address_reference__index
|
||||||
* method GET, get AddressReference Object
|
* method GET, get AddressReference Object
|
||||||
@ -170,5 +205,7 @@ export {
|
|||||||
postAddress,
|
postAddress,
|
||||||
patchAddress,
|
patchAddress,
|
||||||
postPostalCode,
|
postPostalCode,
|
||||||
getAddress
|
getAddress,
|
||||||
|
searchCities,
|
||||||
|
searchReferenceAddresses
|
||||||
};
|
};
|
||||||
|
@ -556,8 +556,8 @@ export default {
|
|||||||
this.entity.selected.address.distribution = this.context.edit ? this.entity.address.distribution: null;
|
this.entity.selected.address.distribution = this.context.edit ? this.entity.address.distribution: null;
|
||||||
this.entity.selected.address.extra = this.context.edit ? this.entity.address.extra: null;
|
this.entity.selected.address.extra = this.context.edit ? this.entity.address.extra: null;
|
||||||
|
|
||||||
this.entity.selected.writeNew.address = this.context.edit;
|
this.entity.selected.writeNew.address = this.context.edit && this.entity.address.addressReference === null && this.entity.address.street.length > 0
|
||||||
this.entity.selected.writeNew.postcode = this.context.edit;
|
this.entity.selected.writeNew.postcode = false // NB: this used to be this.context.edit, but think it was erroneous;
|
||||||
console.log('!! just set writeNew.postcode to', this.entity.selected.writeNew.postcode);
|
console.log('!! just set writeNew.postcode to', this.entity.selected.writeNew.postcode);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -569,7 +569,6 @@ export default {
|
|||||||
applyChanges()
|
applyChanges()
|
||||||
{
|
{
|
||||||
console.log('apply changes');
|
console.log('apply changes');
|
||||||
|
|
||||||
let newAddress = {
|
let newAddress = {
|
||||||
'isNoAddress': this.entity.selected.isNoAddress,
|
'isNoAddress': this.entity.selected.isNoAddress,
|
||||||
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
|
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
|
||||||
@ -633,7 +632,6 @@ export default {
|
|||||||
if (!this.context.edit) {
|
if (!this.context.edit) {
|
||||||
this.addNewAddress(newAddress)
|
this.addNewAddress(newAddress)
|
||||||
.then(payload => this.addressChangedCallback(payload));
|
.then(payload => this.addressChangedCallback(payload));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.updateAddress({
|
this.updateAddress({
|
||||||
addressId: this.context.addressId,
|
addressId: this.context.addressId,
|
||||||
@ -697,8 +695,7 @@ export default {
|
|||||||
* Async PATCH transactions,
|
* Async PATCH transactions,
|
||||||
* then update existing address with backend datas when promise is resolved
|
* then update existing address with backend datas when promise is resolved
|
||||||
*/
|
*/
|
||||||
updateAddress(payload)
|
updateAddress(payload) {
|
||||||
{
|
|
||||||
this.flag.loading = true;
|
this.flag.loading = true;
|
||||||
|
|
||||||
// TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
|
// TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
:taggable="true"
|
:taggable="true"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
@tag="addAddress"
|
@tag="addAddress"
|
||||||
|
:loading="isLoading"
|
||||||
:options="addresses">
|
:options="addresses">
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
</div>
|
</div>
|
||||||
@ -48,14 +49,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VueMultiselect from 'vue-multiselect';
|
import VueMultiselect from 'vue-multiselect';
|
||||||
|
import { searchReferenceAddresses, fetchReferenceAddresses } from '../../api.js';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddressSelection',
|
name: 'AddressSelection',
|
||||||
components: { VueMultiselect },
|
components: { VueMultiselect },
|
||||||
props: ['entity', 'updateMapCenter'],
|
props: ['entity', 'context', 'updateMapCenter'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
value: null
|
value: this.context.edit ? this.entity.address.addressReference : null,
|
||||||
|
isLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -107,6 +111,36 @@ export default {
|
|||||||
},
|
},
|
||||||
listenInputSearch(query) {
|
listenInputSearch(query) {
|
||||||
//console.log('listenInputSearch', query, this.isAddressSelectorOpen);
|
//console.log('listenInputSearch', query, this.isAddressSelectorOpen);
|
||||||
|
if (!this.entity.selected.writeNew.postcode) {
|
||||||
|
if (query.length > 2) {
|
||||||
|
this.isLoading = true;
|
||||||
|
searchReferenceAddresses(query, this.entity.selected.city).then(
|
||||||
|
addresses => new Promise((resolve, reject) => {
|
||||||
|
this.entity.loaded.addresses = addresses.results;
|
||||||
|
this.isLoading = false;
|
||||||
|
resolve();
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error); //TODO better error handling
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (query.length === 0) { // Fetch all cities when suppressing the query
|
||||||
|
this.isLoading = true;
|
||||||
|
fetchReferenceAddresses(this.entity.selected.city).then(
|
||||||
|
addresses => new Promise((resolve, reject) => {
|
||||||
|
this.entity.loaded.addresses = addresses.results;
|
||||||
|
this.isLoading = false;
|
||||||
|
resolve();
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isAddressSelectorOpen) {
|
if (this.isAddressSelectorOpen) {
|
||||||
this.$data.value = { text: query };
|
this.$data.value = { text: query };
|
||||||
} else if (this.isEnteredCustomAddress) {
|
} else if (this.isEnteredCustomAddress) {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
:multiple="false"
|
:multiple="false"
|
||||||
@tag="addPostcode"
|
@tag="addPostcode"
|
||||||
:tagPlaceholder="$t('create_postal_code')"
|
:tagPlaceholder="$t('create_postal_code')"
|
||||||
|
:loading="isLoading"
|
||||||
:options="cities">
|
:options="cities">
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
</div>
|
</div>
|
||||||
@ -48,15 +49,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VueMultiselect from 'vue-multiselect';
|
import VueMultiselect from 'vue-multiselect';
|
||||||
|
import { searchCities, fetchCities } from '../../api.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CitySelection',
|
name: 'CitySelection',
|
||||||
components: { VueMultiselect },
|
components: { VueMultiselect },
|
||||||
props: ['entity', 'focusOnAddress', 'updateMapCenter'],
|
props: ['entity', 'context', 'focusOnAddress', 'updateMapCenter'],
|
||||||
emits: ['getReferenceAddresses'],
|
emits: ['getReferenceAddresses'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
value: null
|
value: this.context.edit ? this.entity.address.postcode : null,
|
||||||
|
isLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -93,6 +96,15 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
console.log('writeNew.postcode', this.entity.selected.writeNew.postcode, 'in mounted');
|
console.log('writeNew.postcode', this.entity.selected.writeNew.postcode, 'in mounted');
|
||||||
|
if (this.context.edit) {
|
||||||
|
this.entity.selected.city = this.value;
|
||||||
|
this.entity.selected.postcode.name = this.value.name;
|
||||||
|
this.entity.selected.postcode.code = this.value.code;
|
||||||
|
this.$emit('getReferenceAddresses', this.value);
|
||||||
|
if (this.value.center) {
|
||||||
|
this.updateMapCenter(this.value.center);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
transName(value) {
|
transName(value) {
|
||||||
@ -105,7 +117,6 @@ export default {
|
|||||||
this.entity.selected.postcode.code = value.code;
|
this.entity.selected.postcode.code = value.code;
|
||||||
this.entity.selected.postcode.coordinates = value.center.coordinates;
|
this.entity.selected.postcode.coordinates = value.center.coordinates;
|
||||||
this.entity.selected.writeNew.postcode = false;
|
this.entity.selected.writeNew.postcode = false;
|
||||||
console.log('writeNew.postcode false, in selectCity');
|
|
||||||
this.$emit('getReferenceAddresses', value);
|
this.$emit('getReferenceAddresses', value);
|
||||||
this.focusOnAddress();
|
this.focusOnAddress();
|
||||||
if (value.center) {
|
if (value.center) {
|
||||||
@ -113,7 +124,33 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
listenInputSearch(query) {
|
listenInputSearch(query) {
|
||||||
//console.log('listenInputSearch', query, this.isCitySelectorOpen);
|
if (query.length > 2) {
|
||||||
|
this.isLoading = true;
|
||||||
|
searchCities(query, this.entity.selected.country).then(
|
||||||
|
cities => new Promise((resolve, reject) => {
|
||||||
|
this.entity.loaded.cities = cities.results.filter(c => c.origin !== 3); // filter out user-defined cities
|
||||||
|
this.isLoading = false;
|
||||||
|
resolve();
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error); //TODO better error handling
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (query.length === 0) { // Fetch all cities when suppressing the query
|
||||||
|
this.isLoading = true;
|
||||||
|
fetchCities(this.entity.selected.country).then(
|
||||||
|
cities => new Promise((resolve, reject) => {
|
||||||
|
this.entity.loaded.cities = cities.results.filter(c => c.origin !== 3); // filter out user-defined cities
|
||||||
|
this.isLoading = false;
|
||||||
|
resolve();
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.isCitySelectorOpen) {
|
if (this.isCitySelectorOpen) {
|
||||||
this.$data.value = { text: query };
|
this.$data.value = { text: query };
|
||||||
} else if (this.isEnteredCustomCity) {
|
} else if (this.isEnteredCustomCity) {
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
<city-selection
|
<city-selection
|
||||||
v-bind:entity="entity"
|
v-bind:entity="entity"
|
||||||
|
v-bind:context="context"
|
||||||
v-bind:focusOnAddress="focusOnAddress"
|
v-bind:focusOnAddress="focusOnAddress"
|
||||||
v-bind:updateMapCenter="updateMapCenter"
|
v-bind:updateMapCenter="updateMapCenter"
|
||||||
@getReferenceAddresses="$emit('getReferenceAddresses', selected.city)">
|
@getReferenceAddresses="$emit('getReferenceAddresses', selected.city)">
|
||||||
@ -37,6 +38,7 @@
|
|||||||
|
|
||||||
<address-selection v-if="!isNoAddress"
|
<address-selection v-if="!isNoAddress"
|
||||||
v-bind:entity="entity"
|
v-bind:entity="entity"
|
||||||
|
v-bind:context="context"
|
||||||
v-bind:updateMapCenter="updateMapCenter">
|
v-bind:updateMapCenter="updateMapCenter">
|
||||||
</address-selection>
|
</address-selection>
|
||||||
|
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class AddressReferenceApiControllerTest extends WebTestCase
|
||||||
|
{
|
||||||
|
use PrepareClientTrait;
|
||||||
|
|
||||||
|
public function provideData()
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
/** @var EntityManagerInterface $em */
|
||||||
|
$em = self::$container->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
$postalCode = $em->createQueryBuilder()
|
||||||
|
->select('pc')
|
||||||
|
->from(PostalCode::class, 'pc')
|
||||||
|
->where('pc.origin = :origin')
|
||||||
|
->setParameter('origin', 0)
|
||||||
|
->setMaxResults(1)
|
||||||
|
->getQuery()
|
||||||
|
->getSingleResult();
|
||||||
|
|
||||||
|
yield [$postalCode->getId(), 'rue'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideData
|
||||||
|
*/
|
||||||
|
public function testSearch(int $postCodeId, string $pattern)
|
||||||
|
{
|
||||||
|
$client = $this->getClientAuthenticated();
|
||||||
|
|
||||||
|
$client->request(
|
||||||
|
'GET',
|
||||||
|
"/api/1.0/main/address-reference/by-postal-code/{$postCodeId}/search.json",
|
||||||
|
['q' => $pattern]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
use function json_decode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class PostalCodeApiControllerTest extends WebTestCase
|
||||||
|
{
|
||||||
|
use PrepareClientTrait;
|
||||||
|
|
||||||
|
public function testSearch()
|
||||||
|
{
|
||||||
|
$client = $this->getClientAuthenticated();
|
||||||
|
|
||||||
|
$client->request(
|
||||||
|
'GET',
|
||||||
|
'/api/1.0/main/postal-code/search.json',
|
||||||
|
['q' => 'fontenay le comte']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
$this->assertEquals('Fontenay Le Comte', $data['results'][0]['name']);
|
||||||
|
|
||||||
|
// test response with invalid search pattern
|
||||||
|
$client->request(
|
||||||
|
'GET',
|
||||||
|
'/api/1.0/main/postal-code/search.json',
|
||||||
|
['q' => '']
|
||||||
|
);
|
||||||
|
$this->assertResponseStatusCodeSame(400);
|
||||||
|
}
|
||||||
|
}
|
@ -360,6 +360,40 @@ paths:
|
|||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
/1.0/main/address-reference/by-postal-code/{id}/search.json:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- address
|
||||||
|
- search
|
||||||
|
summary: Return a reference address by id
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The reference address id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
- name: q
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: The search pattern
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AddressReference'
|
||||||
|
404:
|
||||||
|
description: "not found"
|
||||||
|
401:
|
||||||
|
description: "Unauthorized"
|
||||||
|
400:
|
||||||
|
description: "Bad request"
|
||||||
/1.0/main/postal-code.json:
|
/1.0/main/postal-code.json:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -430,6 +464,37 @@ paths:
|
|||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
/1.0/main/postal-code/search.json:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- address
|
||||||
|
- search
|
||||||
|
summary: Search a postal code
|
||||||
|
parameters:
|
||||||
|
- name: q
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: The search pattern
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: country
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The country id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PostalCode'
|
||||||
|
404:
|
||||||
|
description: "not found"
|
||||||
|
400:
|
||||||
|
description: "Bad Request"
|
||||||
|
|
||||||
/1.0/main/country.json:
|
/1.0/main/country.json:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Main;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20211125142016 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_address_reference_on_insert ON chill_main_address_reference');
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_address_reference_on_update ON chill_main_address_reference');
|
||||||
|
$this->addSql('DROP FUNCTION canonicalize_address_reference()');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_address_reference DROP COLUMN addressCanonical');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add a column "canonicalized" on chill_main_address_reference and add trigger and indexed on it';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_main_address_reference ADD addressCanonical TEXT DEFAULT \'\' NOT NULL');
|
||||||
|
|
||||||
|
$this->addSql('UPDATE chill_main_address_reference
|
||||||
|
SET addresscanonical =
|
||||||
|
TRIM(
|
||||||
|
UNACCENT(
|
||||||
|
LOWER(
|
||||||
|
street ||
|
||||||
|
\' \' ||
|
||||||
|
streetnumber
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)');
|
||||||
|
|
||||||
|
$this->addSql('CREATE OR REPLACE FUNCTION public.canonicalize_address_reference() RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
NEW.addresscanonical =
|
||||||
|
TRIM(
|
||||||
|
UNACCENT(
|
||||||
|
LOWER(
|
||||||
|
NEW.street ||
|
||||||
|
\' \' ||
|
||||||
|
NEW.streetnumber
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
return NEW;
|
||||||
|
END
|
||||||
|
$$');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TRIGGER canonicalize_address_reference_on_insert
|
||||||
|
BEFORE INSERT
|
||||||
|
ON chill_main_address_reference
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE procedure canonicalize_address_reference()');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TRIGGER canonicalize_address_reference_on_update
|
||||||
|
BEFORE UPDATE
|
||||||
|
ON chill_main_address_reference
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE procedure canonicalize_address_reference()');
|
||||||
|
|
||||||
|
$this->addSql('CREATE INDEX chill_internal_address_reference_canonicalized ON chill_main_address_reference USING GIST (postcode_id, addressCanonical gist_trgm_ops)');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Main;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20211125142017 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_postal_code_on_insert ON chill_main_postal_code');
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_postal_code_on_update ON chill_main_postal_code');
|
||||||
|
$this->addSql('DROP FUNCTION canonicalize_postal_code()');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_postal_code DROP COLUMN canonical');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add a column "canonicalized" on postal code';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_main_postal_code ADD canonical TEXT DEFAULT \'\' NOT NULL');
|
||||||
|
|
||||||
|
$this->addSql('UPDATE chill_main_postal_code
|
||||||
|
SET canonical =
|
||||||
|
TRIM(
|
||||||
|
UNACCENT(
|
||||||
|
LOWER(
|
||||||
|
code ||
|
||||||
|
\' \' ||
|
||||||
|
label
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)');
|
||||||
|
|
||||||
|
$this->addSql('CREATE OR REPLACE FUNCTION public.canonicalize_postal_code() RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
NEW.canonical =
|
||||||
|
TRIM(
|
||||||
|
UNACCENT(
|
||||||
|
LOWER(
|
||||||
|
NEW.code ||
|
||||||
|
\' \' ||
|
||||||
|
NEW.label
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
return NEW;
|
||||||
|
END
|
||||||
|
$$');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TRIGGER canonicalize_postal_code_on_insert
|
||||||
|
BEFORE INSERT
|
||||||
|
ON chill_main_postal_code
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE procedure canonicalize_postal_code()');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TRIGGER canonicalize_postal_code_on_update
|
||||||
|
BEFORE UPDATE
|
||||||
|
ON chill_main_postal_code
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE procedure canonicalize_postal_code()');
|
||||||
|
|
||||||
|
$this->addSql('CREATE INDEX chill_internal_postal_code_canonicalized ON chill_main_postal_code USING GIST (canonical gist_trgm_ops) WHERE origin = 0');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user